Введение в GIT
Русский перевод книги Pro Git. Самый полезный и лучший источник информации.
git
-- система контроля изменений (версий). Позволяет отслеживать изменения исходного кода приложения, вносимые в разное время разными пользователями в разные файлы, производить восстановление до более ранней версии, делать слияние изменений в коде при параллельной разработке и т.д.
Установка
! Рассматривается только версия для Windows. !
Для Windows существует следующий дистрибутив. Он содержит утилиту командной строки (Git Bash
) и простенький графический интерфейс (Git GUI
). Ни один из известных графических интерфейсов (в т.ч. Git GUI
) не предоставляет полной свободы действий, потому от его использования стоит отказаться. При установке, однако, следует выбрать пункт меню, добавляющий в контекстное меню проводника команды git (Git Bash Here
, Git Init Here
и т.д.).
Система git очень дружелюбна к пользователю. В случае возникновения ошибок, в случае опечаток и т.д. она предложит варианты устранения проблемы. Обычно достаточно проследовать этим инструкциям для разрешения проблемы.
В частности, при попытке выполнения первого коммита система предложит настроить учетную запись, состоящее из логина и почтового адреса:
$ git config --global user.name "John Doe"
$ git config --global user.email "[email protected]"
Основы работы
Современные IDE, такие как Visual Studio, NetBeans, Eclipse, уже имеют поддержку git на примитивном уровне. Для повседневной качественной работы, однако, часто требуется (в некоторых случаях необходимо) использовать средства Git Bash
.
Рассмотрим по порядку базовые команды git. Будем полагать, что работа ведется в Git Bash
.
Клонирование репозитория
Репозиторий -- единица персистентности системы Git. В репозитории обычно находится один проект или несколько связанных проектов.
В сети существует много провайдеров, предоставляющих возможности создания (удаленных) git-репозиториев. В частности, это GitHub. В качестве примера будем рассматривать именно github.
Для того, чтобы была возможность работать с репозиторием (и, соответственно, его содержимым) на локальном компьютере, необходимо получить локальную копию репозитория. Этим занимается команда clone
.
git clone "https://github.com/kalaider/act-photo-random/"
Результат команды -- папка act-photo-random
(и ее содержимое) в текущей рабочей директории.
При необходимости поместить содержимое репозитория в другую директорию, она указывается следующим параметром:
git clone "https://github.com/kalaider/act-photo-random/" "whatever/you/type"
Результат выполнения команды -- папка, путь до которой -- whatever/you/type
.
Во всех дальнейших примерах будем исходить из того, что текущий рабочий каталог -- как раз папка с репозиторием (act-photo-random
).
Выкачивание изменений из удаленного репозитория
git pull
Данная команда автоматически выкачивает изменения из удаленного репозитория и сливает их с локальными. При наличии конфликтов принимаются меры по их устранению.
Создание локальных изменений
После изменения файла необходимо зафиксировать сделанные в нем изменения. Производится это в два этапа.
На первом файлы добавляются в индекс командой
git add "my/file1.cpp"
git add "my/file2.cpp"
Также в индекс могут быть добавлены сразу все измененные файлы или файлы, удовлетворяющие указанной маске:
git add '*.cpp'
Проверить репозиторий на локальные изменения можно командой git status
. Она выведет на экран все файлы, содержащие какие-либо изменения относительно текущего коммита.
На втором все добавленные изменения фиксируются. Производится создание нового коммита.
git commit -m "Commit message - a text describing the new commit"
После выполнения команды будет создан новый коммит, а в истории изменений репозитория появится новая запись. Просмотреть историю коммитов можно следующим образом:
git log
Пример вывода команды:
commit 12e46ef16e9fd7500d28ce1580ea5578779d75a7
Author: kalaider <[email protected]>
Date: Wed Mar 8 17:37:09 2017 +0300
Readme added
commit 20b976e66a2e2e724760c68a94338e31b6939d24
Author: kalaider <[email protected]>
Date: Wed Mar 8 16:32:51 2017 +0300
ATmega8A component added
commit f284766b0eda5473238139809f2dda8f030cec8a
Author: kalaider <[email protected]>
Date: Wed Mar 8 15:38:08 2017 +0300
Initial
Коммиты идентифицируются своими хэшами (большими шестнадцатеричными числами).
Внесение изменений в удаленном репозитории
Локальные изменения заливаются в удаленный репозиторий командой
git push
Все конфликты при этом разрешаются руками.
Откат к предшествующим состояниям
Для отката изменений (добавленных или не добавленных в индекс, перемотка к более ранним коммитам) осуществляется командой
git reset <начало хэша коммита, имя тега, название ветки т.д.>
После этого все локальные изменения (по сравнению с целевым состоянием отката) будут помечены как не проиндексированные. Для физического отката всех таких не проиндексированных изменений к команде добавляется опция --hard
:
git reset --hard <начало хэша коммита, имя тега, название ветки т.д.>
Теперь репозиторий находится ровно в том состоянии, в котором он был на момент цели отката.
Пример отката к коммиту Initial
:
git reset --hard f28476
Работа с ветками
Ветка (branch) -- альтернатива развития проекта. Она может быть создана для исправления ошибки в определенной версии продукта, для внесения экспериментальных изменений в код без "порчи" основной ветки разработки и т.д.
Создание ветки:
git checkout -b my-new-branch
Команда создает ветвь (на это указывает опция -b
) и устанавливает текущую ветвь (checkout
) в my-new-branch
.
Для переключения между ветками используется команда checkout
:
git checkout master
master
-- главная ветка разработки любого git-репозитория.
Слияние веток
После успешного завершения разработки фичи (устранения бага) изменения должны быть слиты с основной веткой разработки (или просто другой веткой). Существует несколько типов слияния. Рассмотрим только merge
.
Команда merge
создает новый коммит, который получается слиянием текущего состояния текущей ветки с состоянием сливаемой ветки.
git checkout master
git merge my-new-branch
Данная команда переключается на ветку master
и сливает в нее ветку my-new-branch
. После успешного слияния сливаемая ветка может быть удалена:
git branch -d my-new-branch
При конфликте изменений они разводятся руками.
Еще раз о git push
и git pull
Команда push
сливает текущую ветку с удаленной веткой (указанной или установленной по умолчанию). Так что результат команды push
зависит от текущей ветки. Мало чем отличается и поведение pull
.
Подмодули
git
имеет очень хороший инструмент работы с зависимостями, лежащими также в git
-репозиториях. Он называется submodules
.
Подмодуль -- тот же репозиторий, находящийся внутри данного репозитория, который способен быстро и просто подтягивать изменения из внешнего удаленного репозитория.
Это крайне полезно, если проект разделен на несколько связанных модулей, одни из которых являются зависимостями для других. Как выразился товарищ @agladysh в комментарии к этой статье, подмодули отличаются от простого копирования внешних зависимостей тем, что...
...подмодули на самом деле попытка «легитимизации» такого подхода.
И это действительно так. Подмодули хранят кодовое дерево другого репозитория в поддиректории данного репозитория с дополнительными возможностями обновления до произвольного состояния удаленного внешнего репозитория и внесения изменений в удаленный внешний репозиторий.
Добавление репозитория как подмодуля
Для добавления в репозиторий A
репозитория B
в качестве подмодуля в директорию /lib/module-B
необходимо выполнить единственную команду:
git submodule add <URL-or-path-to-module-B> /lib/module-B
Например, добавим исходный код самого git
'а в качестве подмодуля к нашему репозиторию:
git submodule add https://github.com/git/git /lib/git
Эта команда создаст файл .gitmodules
в корне текущего репозитория со следующим содержанием:
[submodule "lib/git"]
path = lib/git
url = https://github.com/git/git
Для того, чтобы подмодуль действительно появился в репозитории, необходимо выполнить еще две команды:
git submodule init
git submodule update --remote
Первая команда инициализирует пустой репозиторий для подмодуля. Вторая подтянет текущее состояние подмодуля в новый репозиторий.
Для того, чтобы зафиксировать сделанные изменения (добавление одного подмодуля и добавление в него коммитов из удаленного репозитория), необходимо выполнить еще две команды:
git add .gitmodules lib/git
git commit -m "New submodule added and its state fetched"
Выкачивание изменений подмодуля
Для выкачивания изменений следует выполнить уже известный git submodule update --remote
и зафиксировать изменения в lib/git
командами:
git submodule update --remote
git add lib/git
git commit -m "git submodule state updated to latest"
Клонирование репозиториев с подмодулями
В случае клонирования репозитория с подмодулями подмодули автоматически склонированы не будут. Не будут они даже инициализированы. Поэтому следует выполнять не одно, а несколько действий, что весьма утомительно:
git clone "https://github.com/someuser/repo-with-submodules.git"
git submodule init
git submodule update
Обратите внимание на то, что флаг --remote
у git submodule update
отсутствует. Это необходимо для того, чтобы получить именно то состояние подмодуля, которое соответствует текущему состоянию данного репозитория, а не последнее состояние удаленного репозитория, на который ссылается подмодуль.
Однако можно поступать гораздо проще, используя специальный флаг --recurse-submodules
команды clone
:
git clone --recurse-submodules "https://github.com/someuser/repo-with-submodules.git"
Это полный эквивалент предыдущего трехкомандного варианта.
Внесение изменений в подмодули
Команда git submodule update --remote
получает репозиторий подмодуля в т.н. состоянии detached head
(отделенная голова), которое фактически означает, что никакая ветка в данный момент не выбрана, а указатель HEAD
указывает не конец какой-то ветки, а на конкретный коммит. Для внесения изменений это недопустимо, поскольку изменения могут вноситься только в ветки.
Для исправления ситуации требуется вручную переключиться на нужную ветку и получить ее состояние:
git submodule update --remote
git checkout master
git pull
Или
git submodule update --remote
git checkout master
git fetch
git merge
Но для этой простой задачи давно уже существует более короткий синоним:
git submodule update --remote --merge master
Он отличается от обычного git submodule update --remote
дополнительным флагом --merge
, которому в качестве аргумента можно передать ветку.
Git LFS
Git Large File Storage
-- CDN
, предоставляющая свои услуги по хранению больших файлов вне репозитория git
, но при этом связанных с текущим состоянием репозитория, легко получаемых вместе с любым выкачиванием состояния репозитория и автоматически размещаемых в Git LFS CDN
при любой выгрузке состояния репозитория.
Git LFS
-- отдельная сторонняя утилита, требуюшая отдельной установки и настройки.
Репозитории, использующие Git LFS
, уже настроены правильно, так что все, что требуется от пользователя, -- установить Git LFS
и склонировать репозиторий обычным образом.
Алгоритм работы утилиты следующий:
- При коммите изменений программа заменяет все реальные файлы, управляемые
Git LFS
, текстовыми файлами, содержащими хэши файлов и некоторые дополнительные параметры. Далее она загружает реальные файлы в своюCDN
, где они доступны как раз по своим хэшам. - При загрузке изменений утилита считывает эти текстовые файлы, скачивает реальные файлы из своей
CDN
, заменяет ими текстовые файлы в рабочем каталоге.
Итого, сами файлы хранятся вне репозитория, а в нем -- только текстовые фейковые файлы с теми же именами. Отсюда дополнительная "проблема" -- команда git archive
, пакующая репозиторий в .zip
-архив более не работает корректно: вместо реальных файлов, управляемых Git LFS
, в архиве будут присутствовать только их хэши.
Настройка Git LFS
Инициализация работы с Git LFS
актуальна только для репозиториев, в данный момент не оскверненных данной утилитой. Для инициализации достаточно выполнить
git lfs init
Это добавит хуки в репозиторий, позволяющие Git LFS
перехватывать совершение коммита, клонирование и т.д.
Добавление файлов под управление Git LFS
осуществляется правкой файла .gitattributes
. Например, для добавления целой директории /doc
под управление утилиты в .gitattributes
следует прописать строчку:
doc/* filter=lfs diff=lfs merge=lfs -text
На этом конфигурирование заканчивается. Производится фиксация изменений обычным коммитом файла .gitattributes
:
git add .gitattributes
git commit -m "doc directory is now managed by Git LFS utility"
git push
Правила хорошего тона при работе с репозиториями
- Не использовать ветку
master
в качестве единственной ветки разработки. Веткаmaster
-- почти что ранний релиз продукта. В ней должен располагаться максимально стабильный код. - Использовать Pull-реквесты при необходимости внесения изменений в ветку
master
из другой ветки. После одобрения pull-request'а изменения сливаются администратором проекта в общую ветку. - Не коммитить в чужие ветки без разрешения автора ветки.
- Не плодить тонны веток в удаленном репозитории (в локальном, разумеется, никто не запрещает).
- Удалять ветки только когда они действительно больше не нужны, т.е. слиты в
master
или другую ветку. - Оставлять внятные сообщения коммитов (желательно на едином для проекта и участников проекта (предпочтительно английском) языке для однородности и понимания друг друга; более того, это устраняет необходимость постоянно переключать раскладку клавиатуры для ввода команд и ввода текста)