Предисловие

Git - самая популярная распределённая система контроля версиями. Основное предназначение Git – это сохранение снимков последовательно улучшающихся состояний вашего проекта.


Настройка git

Прежде чем начинать работу с git необходимо его настроить под себя!

Конфигурационные файлы

  • /etc/gitconfig - Общие настройки для всех пользователей и репозиториев;
  • ~/.gitconfig или ~/.config/git/config - Настройки конкретного пользователя;
  • .git/config - Настройки для конкретного репозитория;

Есть специальная команда

git config [<опции>]

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

В зависимости какой параметр вы передадите команде git config (--system, --global, --local), настройки будут записываются в один из этих файлов. Каждый из этих “уровней” (системный, глобальный, локальный) переопределяет значения предыдущего уровня!

Что бы посмотреть в каком файле, какие настройки установлены используйте

git config --list --show-origin

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

В git вы сами решаете какие файлы и в какой коммит попадут, но возможно вы бы хотели, что бы определённые файлы никогда не попали в индекс и в коммит, да и вообще не отображались в списке не отслеживаемых. Для этого вы можете создать специальный файл (.gitignore) в вашем репозитории и записать туда шаблон игнорируемых файлов. Если вы не хотите создавать такой файл в каждом репозитории вы можете определить его глобально с помощью core.excludesfile. Вы также можете скачать готовый .gitignore file для языка программирования на котором вы работаете.

Для настройки .gitignore используйте регулярные выражения bash.

Настройки по умолчанию

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

Используйте

git config name value

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

Пример:

git config --global core.editor nano

установит редактор по умолчанию nano.

Вы можете посмотреть значение существующего параметра с помощью git config --get [name] где name это параметр, значение которого вы хотите получить.

Например

git config --get core.editor

Полезные настройки:

  • user.name - Имя, которое будет использоваться при создании коммита;
  • user.email - Email, который будет использоваться при создании коммита;
  • core.excludesfile - Файл, шаблон которого будет использоваться для игнорирования определённых файлов глобально;
  • core.editor - Редактор по умолчанию;
  • commit.template - Файл, содержимое которого будет использоваться для сообщения коммита по умолчанию;
  • help.autocorrect - При установке значения 1, git будет выполнять неправильно написанные команды;
  • credential.helper [mode] - Устанавливает режим хранения учётных данных. [cache] - учётные данные сохраняются на определённый период, пароли не сохраняются (--timeout [seconds] количество секунд после которого данные удаляются, по умолчанию 15 мин). [store] - учётные данные сохраняются на неограниченное время в открытом виде (--file [file] указывает путь для хранения данных, по умолчанию ~/.git-credentials).

Псевдонимы (aliases)

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

git config alias.SHORT_NAME COMMAND

где SHORT_NAME это имя для сокращения, а COMMAND команда(ы) которую нужно сократить. Пример:

git config --global alias.last 'log -1 HEAD'

после выполнения этой команды вы можете просматривать информацию о последнем коммите на текущей ветке выполнив git last.

Я советую вам использовать следующие сокращения (вы также можете определить любые свои):

  • st = status
  • ch = checkout
  • br = branch
  • mg = merge
  • cm = commit
  • reb = rebase
  • lg = «git log --pretty=format:'%h - %ar: %s'»

Для просмотра настроек конфигурации используйте: git config --list.


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

  • git init [<опции>] - Создаёт git репозитории и директорию .git в текущей директории (или в директории указанной после --separate-git-dir <каталог-git>, в этом случае директория .git будет находится в другом месте);
  • git clone [<опции>] [--] <репозиторий> [<каталог>] [-o, --origin <имя>] [-b, --branch <ветка>] [--single-branch] [--no-tags] [--separate-git-dir <каталог-git>] [-c, --config <ключ=значение>] - Клонирует репозитории с названием origin (или с тем которое вы укажите -o <имя>), находясь на той ветке, на которую указывает HEAD (или на той которую вы укажите -b <ветка>). Также вы можете клонировать только необходимую ветку HEAD (или ту которую укажите в -b <ветка>) указав --single-branch. По умолчанию клонируются все метки, но указав --no-tags вы можете не клонировать их. После выполнения команды создаётся директория .git в текущей директории (или в директории указанной после --separate-git-dir <каталог-git>, в этом случае директория .git будет находится в другом месте);

Состояние файлов

Для просмотра состояния файлов в вашем репозитории используйте:

git status [<опции>]

Эта команда может показать вам: на какой ветке вы сейчас находитесь и состояние всех файлов. Обязательных опций нет, из полезных можно выделить разве что -s которая покажет краткое представление состояний файлов.

Жизненный цикл файлов

Как видно на картинке файлы могут быть не отслеживаемые (Untracked) и отслеживаемые.

Отслеживаемые файлы могут находится в 3 состояниях: Не изменено (Unmodified), изменено (Modified), подготовленное (Staged).

Если вы добавляете (с помощью git add) «Не отслеживаемый» файл, то он переходит в состояние «Подготовлено».

Если вы изменяете файл в состоянии «Не изменено», то он переходит в состояние «Изменено». Если вы сохраняете изменённый файл (то есть находящийся в состоянии «Изменено») он переходит в состояние «Подготовлено». Если вы делаете коммит файла (то есть находящийся в состоянии «Подготовлено») он переходит в состояние «Не изменено».

Если версии файла в HEAD и рабочей директории отличаются, то файл будет находится в состоянии «Изменено», иначе (если версия в HEAD и в рабочем каталоге одинакова) файл будет находится в состоянии «Не изменено».

Если версия файла в HEAD отличается от рабочего каталога, но не отличается от версии в индексе, то файл будет в состоянии «Подготовлено».

Этот цикл можно представить следующим образом:

Unmodified -> Modified -> Staged -> Unmodified

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

Работа с индексом

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

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

Что бы просмотреть индекс, используйте git status.

Что бы добавить файлы в индекс используйте:

git add [<опции>]

Полезные параметры команды git add:

  • -f, --force - добавить также игнорируемые файлы;
  • -u, --update - обновить отслеживаемые файлы;

Что бы удалить файлы из индекса вы можете использовать 2 команды git reset и git restore.

  • git-restore - восстановит файлы рабочего дерева.
  • git-reset - сбрасывает текущий HEAD до указанного состояния.

По сути вы можете добиться одного и того же с помощью обеих команд.

Что бы удалить из индекса некоторые файлы используйте:

git restore --staged <file>

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

Собственно разработчики рекомендуют для сброса индекса использовать именно git restore -S. Вместо git reset HEAD.

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

git diff [<options>]

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

Пример:

git diff 00656c 3d5119

покажет различия между коммитом 00656c и 3d5119.

Работа с коммитами

Теперь, когда ваш индекс находится в нужном состоянии, пора сделать коммит ваших изменений. Запомните, что все файлы для которых вы не выполнили git add после момента редактирования - не войдут в этот коммит. На деле файлы в нём будут, но только их старая версия (если таковая имеется).

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

git commit [<опции>]

Полезные опции команды git commit:

  • -F, --file [file] - Записать сообщение коммита из указанного файла
  • --author [author] - Подменить автора коммита
  • --date [date] - Подменить дату коммита
  • -m, --mesage [message] - Сообщение коммита
  • -a, --all - Закоммитеть все изменения в файлах
  • -i, --include [files...] - Добавить в индекс указанные файлы для следующего коммита
  • -o, --only [files...] - Закоммитеть только указанные файлы
  • --amend - Перезаписать предыдущий коммит

Вы можете определить сообщение для коммита по умолчанию с помощью commit.template. Эта директива в конфигурационном файле отвечает за файл содержимое которого будет использоваться для коммита по умолчанию. Пример: git config --global commit.template ~/.gitmessage.txt.

Вы также можете изменить, удалить, объединить любой коммит.

Как вы уже могли заметить вы можете быстро перезаписать последний коммит с помощью git commit --amend.

Для изменения коммитом в вашей истории используйте:

git rebase -i <commit>

где commit это верхний коммит в вашей цепочке с которого вы бы хотели что либо изменить.

После выполнения git rebase -i в интерактивном меню выберите что вы хотите сделать.

  • pick <коммит> = использовать коммит
  • reword <коммит> = использовать коммит, но изменить сообщение коммита
  • edit <коммит> = использовать коммит, но остановиться для исправления
  • squash <коммит> = использовать коммит, но объединить с предыдущим коммитом
  • fixup <коммит> = как «squash», но пропустить сообщение коммита
  • exec <команда> = выполнить команду (остаток строки) с помощью командной оболочки
  • break = остановиться здесь (продолжить с помощью «git rebase --continue»)
  • drop <коммит> = удалить коммит
  • label <метка> = дать имя текущему HEAD
  • reset <метка> = сбросить HEAD к указанной метке

Просмотр истории

С помощью команды

git log [<опции>] [<диапазон-редакций>]

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

Полезные параметры команды git log:

  • -p - Показывает разницу для каждого коммита.
  • --stat - Показывает статистику измененных файлов для каждого коммита.
  • --graph - Отображает ASCII граф с ветвлениями и историей слияний.

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

  • -(n) - Показывает только последние n коммитов.
  • --since, --after - Показывает коммиты, сделанные после указанной даты.
  • --until, --before - Показывает коммиты, сделанные до указанной даты.
  • --author - Показывает только те коммиты, в которых запись author совпадает с указанной строкой.
  • --committer - Показывает только те коммиты, в которых запись committer совпадает с указанной строкой.
  • --grep - Показывает только коммиты, сообщение которых содержит указанную строку.
  • -S - Показывает только коммиты, в которых изменение в коде повлекло за собой добавление или удаление указанной строки.

Также вы можете настроить свои формат вывода коммитов с помощью

git log --fotmat:["format"]

Варианты форматирования для git log --format.

  • %H - Хеш коммита
  • %h - Сокращенный хеш коммита
  • %T - Хеш дерева
  • %t - Сокращенный хеш дерева
  • %P - Хеш родителей
  • %p - Сокращенный хеш родителей
  • %an - Имя автора
  • %ae - Электронная почта автора
  • %ad - Дата автора (формат даты можно задать опцией --date=option)
  • %ar - Относительная дата автора
  • %cn - Имя коммитера
  • %ce - Электронная почта коммитера
  • %cd - Дата коммитера
  • %cr - Относительная дата коммитера
  • %s - Содержание

Пример:

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

покажет список коммитов состоящий из хэша времени и сообщения коммита.

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

Так как git это распределённая VCS, вы можете работать не только с локальными но и с внешними репозиториями. Удалённые репозитории представляют собой версии вашего проекта, сохранённые на внешнем сервере.

Для работы с внешними репозиториями используйте:

git remote [<options>]

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

git remote add [<options>] <name> <address>

Вы можете тут же извлечь внешние ветки с помощью -f, --fetch (вы получите имена и состояние веток внешнего репозитория). Вы можете настроить репозитории только на отправку или получение данных с помощью --mirror[=(push|fetch)]. Для получения меток укажите --tags.

Для просмотра подключённых внешних репозиториев используйте git remote без аргументов или git remote -v для просмотра адресов на отправку и получение данных от репозитория.

Для загрузки данных с внешнего репозитория используйте git pull [rep] [branch]. Если ваши ветки отслеживают внешние, то можете не указывать их при выполнении git pull. По умолчанию вы получите данные со всех отслеживаемых веток.

Для загрузки веток на новую ветку используйте git checkout -b <new_branch_name> <rep/branch>.

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

git push [<rep>] [<br>]

где rep это название внешнего репозитория, а br локальная ветка которую вы хотите отправить. Также вы можете использовать такую запись git push origin master:dev. Таким образом вы выгрузите вашу локальную ветку master на origin (но там она будет называться dev).

Для удаления внешних веток используйте

git push origin --delete branch_name

Для получения подробной информации о внешнем репозитории (адреса для отправки и получения, на что указывает HEAD, внешние ветки, локальные ветки настроенные для git pull и локальные ссылки настроенные для git push)

git remote show <remote_name>

Для переименования названия внешнего репозитория используйте

git remote rename <last_name> <new_name>

Для удаления ссылки на внешний репозиторий используйте

git remote rm <name>

Ветвление в git

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

Базовые операции

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

git branch <branch_name> [<start_commit>]

Здесь branch_name - это название для новой ветки, а start_commit - это коммит на который будет указывать ветка (то есть последний коммит в ней). По умолчанию, ветка будет находиться на последнем коммите родительской ветки.

Опции git branch:

  • -r | -a [--merged | --no-merged] - Список отслеживаемых внешних веток -r. Список и отслеживаемых и локальных веток -a. Список слитых веток --merged. Список не слитых веток --no-merged.
  • -l, -f <имя-ветки> [<точка-начала>] - Список имён веток -l. Принудительное создание, перемещение или удаление ветки -f. Создание новой ветки <имя ветки>.
  • -r (-d | -D) - Выполнить действие на отслеживаемой внешней ветке -r. Удалить слитую ветку -d. Принудительное удаление (даже не слитой ветки) -D.
  • -m | -M [<Старая ветка>] <Новая ветка> - Переместить/переименовать ветки и ее журнал ссылок (-m). Переместить/переименовать ветку, даже если целевое имя уже существует -M.
  • (-с | -С) [<старая-ветка>] <новая-ветка> - Скопировать ветку и её журнал ссылок -c. Скопировать ветку, даже если целевое имя уже существует -C.
  • -v, -vv - Список веток с последним коммитом на ветке -v. Список и состояние отслеживаемых веток с последним коммитом на них.

Больше информации смотрите в git branch -h | --help.

Для переключения на ветку используйте git checkout. Также вы можете создать ветку выполнив git checkout -b <ветка>.

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

Для слияния двух веток git репозитория используйте git merge.

Полезные параметры для git merge:

  • --squash - Создать один коммит вместо выполнения слияния. Если у вас есть конфликт на ветках, то после его устранения у вас на ветке прибавится 2 коммита (коммит с сливаемой ветки + коммит слияния), но указав этот аргумент у вас прибавится только один коммит (коммит слияния).
  • --ff-only - Не выполнять слияние если имеется конфликт.
  • -X [strategy] - Использовать выбранную стратегию слияния.
  • --abort - Отменить выполнение слияния.

Процесс слияния

Если вы не выполняли на родительской ветке новых коммитов, то слияние сводится к быстрой перемотке «fast-forward», будто бы вы не создавали новую ветку, а все изменения происходили прямо тут (на родительской ветке).

Если вы выполняли коммиты на обеих ветках, но при этом не создали конфликт, то слияние пройдёт в «recursive strategy», то есть вам просто нужно будет создать коммит слияния, чтобы применить изменения (используйте опцию --squash, чтобы не создавать лишний коммит).

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

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

<<<<<<< HEAD
Тут будет версия изменения последнего коммита текущей ветки
======
Тут будет версия изменений последнего коммита сливаемой ветки
>>>>>>> Тут название ветки с которой сливаем

Разрешив конфликт вы должны завершить слияния выполнив коммит.

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

git diff --ours - Разница до слияния и после
git diff --theirs - Разница сливаемой ветки до слияния и после
git diff --base - Разница с обеими ветками до слияния и после

Если вы не хотите разрешать слияние, то используйте различные стратегии слияния, выбрав либо «нашу» версию (то есть ту, которая находится на текущей ветке), либо выбрать «их» версию находящуюся на сливаемой ветке, при этом не исправляя конфликт. Выполните git merge --Xours или git merge --Xtheirs соответственно.

Rerere

Rerere - «reuse recorded resolution - повторное использование сохраненных разрешений конфликтов». Механизм rerere способен запомнить каким образом вы разрешали некую часть конфликта в прошлом и провести автоматическое исправление конфликта при возникновении его в следующий раз.

Что бы включить rerere выполните

git config --global rerere.enabled true

Таrже вы можете включить rerere создав каталог .git/rr-cache в нужном репозитории.

Используйте git rerere status для того, чтобы посмотреть для каких файлов rerere сохранил снимки состояния до начала слияния.

Используйте git rerere diff для просмотра текущего состояния конфликта.

Если во время слияния написано: Resolved 'nameFile' using previous resolution. Значит rerere уже устранил конфликт используя кэш.

Для отмены автоматического устранения конфликта используйте git checkout --conflict=merge, таким образом вы отмените авто устранение конфликта и вернёте файл(ы) в состояние конфликта для ручного устранения.


Указатели в git

В git есть такие указатели как HEAD branch. По сути всё очень просто, HEAD указывает на текущую ветку, а ветка указывает на последний коммит в ней. Но для понимания лучше представлять, что HEAD указывает на последний коммит.

Перемещение указателей

Представьте, что Git управляет содержимым трех различных деревьев. Здесь под “деревом” понимается “набор файлов”.

В своих обычных операциях Git управляет тремя деревьями:

  • HEAD - Снимок последнего коммита, родитель следующего
  • Индекс - Снимок следующего намеченного коммита
  • Рабочий Каталог - Песочница

Собственно git предоставляет инструменты для манипулирования всеми тремя деревьями. Далее будет рассмотрена команда git reset, позволяющая работать с тремя деревьями вашего репозитория.

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

  • --soft - Cбросить только HEAD
  • --mixed - Cбросить HEAD и индекс
  • --hard - Cбросить HEAD, индекс и рабочий каталог

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

Пример 1. Вы сделали 3 лишних коммита, каждый из которых приносит маленькие изменения и вы хотите сделать из них один, таким образом вы можете с помощью git reset --soft переместить указатель HEAD, при этом оставив индекс и рабочий каталог нетронутым и сделать коммит. В итоге в вашей истории будет выглядеть так, что все изменения произошли в одном коммите.

Пример 2. Вы добавили в индекс лишние файлы и хотите их оттуда убрать. Для этого вы можете использовать git reset HEAD <files...>. Или вы хотите, чтобы в коммите файлы выглядели как пару коммитов назад. Как я уже говорил ранее, вы можете сбросить индекс на любой коммит в отличии от git restore, который сбрасывает только до последнего коммита. Только с опцией mixed вы можете применить действие к указанному файлу.

Пример 3. Вы начали работать над новой фичей на вашем проекте, но вдруг работадатель говорит, что она более не нужна и вы в порыве злости выполняете git reset --hard возвращая ваш индекс, файлы и HEAD к тому моменту, когда вы ещё не начали работать над фичей. А на следующей день вам говорят, что фичу всё таки стоит запилить. Но что же делать? Как же переместится вперёд, ведь вы откатили все 3 дерева и теперь в истории с помощью git log их не найти. А выход есть - это журнал ссылок git reflog. С помощью этой команды вы можете посмотреть куда указывал HEAD и переместится не только вниз по истории коммитов, но и вверх. Этот журнал является локальным для каждого пользователя.