Git — это консольная утилита для отслеживания и ведения истории изменений файлов в проекте. Чаще всего его используют для работы с кодом, но он подходит и для любых других типов файлов.
Я пользовался Git ещё в университете — он был очень удобен для написания диплома, помогая избежать создания множества версий файлов с названиями вроде: “диплом (новый)”, “диплом (новее нового)”, “диплом (новые правки)”.
Мои статьи основаны на отличном интерактивном курсе LearnGitBranching, но у него есть один существенный недостаток: он не использует настоящий Git в процессе обучения, из-за чего упускаются важные нюансы работы с системой. В моей статье мы будем работать с реальным Git. Просто повторяйте команды, чтобы попрактиковаться.
Что такое VCS?
Системы контроля версий (СКВ, VCS, Version Control Systems) позволяют откатывать проект до предыдущих версий, сравнивать изменения, анализировать их или сливать правки в репозиторий.
Репозиторий — это хранилище вашего кода. Git работает локально, и все репозитории хранятся в определённых папках на вашем жёстком диске. Существуют также облачные хостинги репозиториев, такие как GitHub и GitLab, которые позволяют хранить проект в интернете.
VCS позволяют нескольким разработчикам работать над одним проектом, сохраняя внесённые изменения независимо друг от друга. При этом каждый участник команды может видеть, над чем работают его коллеги.
Теперь, когда мы разобрались, зачем нужен Git, давайте создадим свой первый репозиторий. Но сначала нужно установить Git.
Установка git
Основным интерфейсом для работы с Git является консоль. Хотя это может показаться неудобным в повседневной работе, некоторые задачи можно решить только через консоль.
Установка на Windows
Для установки Git на Windows нажмите на кнопку ниже, скачайте установочный файл и следуйте инструкциям по установке.
Установка на macOS
Откройте терминал и, если у вас установлен Homebrew, выполните команду:
brew install git
Если Homebrew не установлен, введите команду:
git --version
После этого появится окно с предложением установить Command Line Tools (CLT). Согласитесь и дождитесь завершения установки. Вместе с CLT будет установлен и Git.
Установка на Linux
Откройте терминал и выполните команду в зависимости от вашей системы.
# Debian или Ubuntu
sudo apt install git
# CentOS
sudo yum install git
Настройка
После установки Git его необходимо настроить, чтобы при создании коммитов указывался автор изменений.
Откройте терминал (Linux и macOS) или консоль (Windows) и выполните следующие команды:
git config --global user.name "ваше_имя"
git config --global user.email "адрес_почты@email.com"
Инициализация репозитория
Теперь вы готовы начать работу с Git локально на вашем компьютере. Для начала создадим папку, которая будет использоваться в качестве обучающего проекта:
$ mkdir git-project
$ cd git-project
Чтобы создать локальный репозиторий в этой папке, выполните команду:
$ git init
После этого вы увидите сообщение:
Инициализирован пустой репозиторий Git в /home/upagge/Documents/git-project/.git/
Теперь посмотрим, какие файлы находятся в папке git-project
:
$ ls -al
Вывод команды может выглядеть следующим образом:
итого 20
drwxrwxr-x 3 upagge upagge 4096 мая 25 13:04 .
drwxr-xr-x 12 upagge upagge 12288 мая 25 13:02 ..
drwxrwxr-x 7 upagge upagge 4096 мая 25 13:04 .git
В папке появился новый скрытый каталог .git
, который содержит множество файлов и директорий. Давайте разберёмся, для чего они нужны. Чтобы посмотреть содержимое папки .git
, используйте команду:
$ ls -l .git/
Что находится в папке .git?
итого 56
drwxrwxr-x 2 upagge upagge 4096 мая 25 13:04 branches
-rw-rw-r-- 1 upagge upagge 92 мая 25 13:04 config
-rw-rw-r-- 1 upagge upagge 73 мая 25 13:04 description
-rw-rw-r-- 1 upagge upagge 23 мая 25 13:04 HEAD
drwxrwxr-x 2 upagge upagge 4096 мая 25 13:04 hooks
drwxrwxr-x 2 upagge upagge 4096 мая 25 13:04 info
drwxrwxr-x 4 upagge upagge 4096 мая 25 13:04 objects
drwxrwxr-x 4 upagge upagge 4096 мая 25 13:04 refs
- branches — каталог, который хранит информацию о локальных ветках репозитория. В современных версиях Git этот каталог почти не используется, так как ветки управляются по-другому.
- config — файл конфигурации репозитория, содержащий настройки для данного проекта, такие как имя пользователя, адрес электронной почты, указание удалённых репозиториев и другие параметры.
- description — файл, который используется для описания репозитория. Он играет роль только на некоторых хостингах репозиториев и не влияет на локальную работу с Git.
- HEAD — файл, который указывает на текущую активную ветку. Когда вы переключаетесь между ветками, содержимое этого файла обновляется, чтобы Git знал, на какой ветке вы сейчас находитесь.
- hooks — каталог, содержащий скрипты, которые могут запускаться в ответ на определённые события в Git, такие как коммиты, слияния веток или отправка изменений на сервер. Эти скрипты можно настроить для автоматизации процессов.
- info — папка, которая хранит дополнительную информацию о репозитории, например, файлы, исключённые из отслеживания, которые не добавлены в
.gitignore
. - objects — один из самых важных каталогов. В нём хранятся все данные репозитория в виде объектов: файлы, каталоги, коммиты и т.д. Каждый объект идентифицируется уникальным хэшом.
- refs — этот каталог содержит ссылки на различные ветки, теги и другие указатели внутри вашего репозитория.
Коммиты
Коммит — это одно из основных понятий в Git. Если объяснять простым языком, коммит — это снимок состояния вашего проекта на определённый момент времени. Однако Git не копирует весь проект при каждом коммите. Вместо этого он сжимает коммит до набора изменений или «дельты» между текущей версией и предыдущей. Это помогает экономить место.
Кроме того, Git сохраняет информацию о том, когда был сделан каждый коммит и кем, что позволяет отслеживать изменения. Это полезно, например, чтобы выяснить, кто внёс изменения, приведшие к ошибкам 😄.
Области Git
Файлы в репозитории могут находиться в трёх разных “областях”:
- HEAD — это область, которая указывает на последний сделанный коммит.
- Индекс — это черновик коммита, в который вы добавляете изменения перед созданием самого коммита.
- Рабочий каталог — это текущая директория с файлами вашего проекта.
Сейчас наш проект пуст. Давайте создадим первый файл:
echo "12345" > file.txt
На этом этапе файл находится только в рабочем каталоге. Две другие области (HEAD и Индекс) пока пусты.
Рабочий каталог — это ваша основная рабочая папка, в данном случае это git-project
. Две другие области — Индекс и HEAD — хранят своё содержимое внутри папки .git
в специфическом формате, удобном для Git, но не для человека.
Рабочий каталог — это место, где вы можете экспериментировать. Если что-то пошло не так, можно отменить изменения и вернуться к последнему коммиту. Пока изменения не добавлены в Индекс, они не будут сохранены.
Рабочий Каталог
это ваша папка с файлами, в данном случае это git-project
. Две другие области сохраняют свое содержимое внутри папки .git
в понятном и удобном для git формате, но не понятном для человека.
Добавление файлов в Индекс
Чтобы зафиксировать изменения (создать коммит), необходимо сначала добавить файлы из Рабочего каталога в Индекс — это черновик коммита.
Только файлы, добавленные в Индекс, попадут в коммит. Если попытаться создать коммит без добавления файлов в Индекс, Git выведет предупреждение. Попробуйте сами:
git commit -m "Первый коммит"
Вы увидите сообщение:
На ветке main
Начальный коммит
Неотслеживаемые файлы:
file.txt
ничего не добавлено в коммит, но есть неотслеживаемые файлы
Это значит, что файл file.txt
не добавлен в Индекс, и Git его не зафиксировал. Чтобы добавить файл в Индекс, выполните команду:
git add file.txt
Если у вас много файлов, вы можете добавить их все сразу:
git add --all
Теперь сделаем первый коммит, который зафиксирует изменения в области HEAD:
git commit -m "Первый коммит"
Вы увидите вывод:
[main (корневой коммит) 06f7fc0] Первый коммит
1 file changed, 1 insertion(+)
create mode 100644 file.txt
Коммит готов, и файл попал в область HEAD. HEAD всегда указывает на последний сделанный коммит. В дальнейшем HEAD будет “родителем” для следующего коммита.
Самое простое представление о HEAD — это снимок вашего последнего коммита. Возможно, сейчас это не совсем понятно, но мы обсудим HEAD подробнее позднее.
Если сейчас выполнить команду:
git status
Git сообщит, что изменений нет, так как все три области (Рабочий каталог, Индекс и HEAD) синхронизированы.
Внесение изменений и создание нового коммита
Теперь давайте внесём изменения в файл и сделаем новый коммит. Мы пройдём через ту же процедуру. Сначала отредактируем файл в нашем рабочем каталоге. Допишем в файл цифры 67890, назовём эту версию файла v2:
echo "67890" >> file.txt
Теперь проверим статус репозитория:
git status
Git сообщит:
На ветке main
Изменения, которые не в индексе для коммита:
(используйте «git add <файл>…», чтобы добавить файл в индекс)
(используйте «git checkout -- <файл>…», чтобы отменить изменения
в рабочем каталоге)
изменено: file.txt
нет изменений добавленных для коммита
(используйте «git add» и/или «git commit -a»)
Git подсказывает, что изменения не попадут в коммит, пока мы не добавим их в Индекс с помощью команды git add
.
Прежде чем добавить изменения в коммит, давайте создадим новый файл:
echo "qwerty" > new-file.txt
Ещё раз проверим статус репозитория:
git status
На ветке main
Изменения, которые не в индексе для коммита:
(используйте «git add <файл>…», чтобы добавить файл в индекс)
(используйте «git checkout -- <файл>…», чтобы отменить изменения
в рабочем каталоге)
изменено: file.txt
Неотслеживаемые файлы:
(используйте «git add <файл>…», чтобы добавить в то, что будет включено в коммит)
new-file.txt
нет изменений добавленных для коммита
(используйте «git add» и/или «git commit -a»)
Теперь новый файл появился в списке неотслеживаемых, то есть он находится в Рабочем каталоге, но Git его ещё не отслеживает. Давайте добавим в отслеживание этот файл, а также изменённый ранее file.txt
:
$ git add file.txt new-file.txt
Проверим статус репозитория:
git status
Git покажет:
На ветке main
Изменения, которые будут включены в коммит:
(используйте «git reset HEAD <файл>…», чтобы убрать из индекса)
изменено: file.txt
новый файл: new-file.txt
Теперь у нас есть изменения, которые готовы для включения в коммит. Пришло время создать второй коммит:
$ git commit -m "Второй коммит"
После выполнения команды Git отобразит следующее:
[main b66f9c7] Второй коммит
2 files changed, 2 insertions(+)
create mode 100644 new-file.txt
Я уже упоминал, что у каждого коммита есть “родитель”, который указывает на предыдущий коммит. Из таких последовательностей создаётся “ветка”. По умолчанию в Git есть ветка main
. Эта ветка является историей нашего проекта, и мы можем вернуться к любому из предыдущих коммитов в любой момент.
Для удобства представления можно визуализировать коммиты как кружочки, а стрелки между ними будут указывать на родительские коммиты.
Просмотр истории коммитов
Не все коммиты будете делать вы — часть коммитов могут выполнять ваши коллеги по команде. Поэтому вам может понадобиться просмотреть историю коммитов. Одним из основных и наиболее мощных инструментов для этого является команда:
$ git log
Пример вывода:
commit b66f9c79eb37de0beb099b04cc7f7f4979a98a83 (HEAD -> main)
Author: uPagge
Date: Tue Jun 15 21:50:01 2021 +0300
Второй коммит
commit 06f7fc0a337bb6105f6a792fcc93eb2dda2a4dda
Author: uPagge
Date: Tue Jun 15 21:48:54 2021 +0300
Первый коммит
Помимо автора и даты, у каждого коммита есть уникальный идентификатор, который называется hash. Например: 2934ee19f4d4ca37ff9bea9dc8208ef5362d789e
. При работе с хэшами необязательно использовать всю длину: Git понимает их по первым 5-7 символам.
Команда git log
имеет множество опций для поиска и фильтрации коммитов по различным критериям. Один из самых полезных аргументов — это -p
(или --patch
), который показывает разницу, внесённую в каждый коммит. Также можно ограничить количество отображаемых коммитов с помощью параметра -2
. Например:
$ git log -p -2
Пример вывода:
commit b66f9c79eb37de0beb099b04cc7f7f4979a98a83 (HEAD -> main)
Author: uPagge
Date: Tue Jun 15 21:50:01 2021 +0300
Второй коммит
diff --git a/file.txt b/file.txt
index e56e15b..cbb384f 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
12345
+67890
diff --git a/new-file.txt b/new-file.txt
new file mode 100644
index 0000000..19f0805
--- /dev/null
+++ b/new-file.txt
@@ -0,0 +1 @@
+qwerty
commit 06f7fc0a337bb6105f6a792fcc93eb2dda2a4dda
Author: uPagge
Date: Tue Jun 15 21:48:54 2021 +0300
Первый коммит
diff --git a/file.txt b/file.txt
new file mode 100644
index 0000000..e56e15b
--- /dev/null
+++ b/file.txt
@@ -0,0 +1 @@
+12345
Вывод git log -p -2
показывает последние два коммита и изменения, которые были сделаны в каждом из них. В данном случае видно, что в первом коммите был добавлен файл file.txt
с содержимым “12345”, а во втором — изменён этот файл и добавлен новый файл new-file.txt
с текстом “qwerty”.
Навигация
Прежде чем перейти к более продвинутым возможностям Git, важно понять, как перемещаться по коммитам вашего проекта.
Detaching HEAD
Давайте разберёмся, как откатиться к более ранней версии репозитория. В Git есть указатели на коммиты, которые можно представить как ярлыки, перемещающиеся от коммита к коммиту. Одним из таких указателей является HEAD
.
HEAD
— это символическое имя текущего выбранного коммита. По сути, это тот коммит, над которым вы работаете в данный момент. Обычно HEAD указывает на имя ветки (например, main
), но его можно “отделить” (detaching) и привязать напрямую к нужному коммиту с помощью его хэша.
Для примера создадим новый файл и сделаем третий коммит:
$ echo "new new file" > three-file.txt
$ git add three-file.txt
$ git commit -m "Третий коммит"
Теперь посмотрим историю последних двух коммитов:
$ git log -2
Вывод:
commit b64191a1a53c7f28429042c1fe00702c15354fab (HEAD -> main)
Author: uPagge
Date: Tue Jun 15 21:52:20 2021 +0300
Третий коммит
commit b66f9c79eb37de0beb099b04cc7f7f4979a98a83
Author: uPagge
Date: Tue Jun 15 21:50:01 2021 +0300
Второй коммит
Здесь видно, что HEAD
указывает на ветку main
, а main
указывает на коммит b64191a
— наш третий коммит.
Отделение HEAD
означает, что он будет указывать не на ветку, а на конкретный коммит. Представим, что нужно посмотреть состояние репозитория после второго коммита. Для этого используем команду git checkout
и хэш второго коммита:
$ git checkout b66f9
Git выведет:
Примечание: переход на «b66f9».
Вы сейчас в состоянии «отделённого HEAD». Вы можете осмотреться, сделать
экспериментальные изменения и закоммитить их, также вы можете отменить
изменения любых коммитов в этом состоянии не затрагивая любые ветки и
не переходя на них.
Если вы хотите создать новую ветку и сохранить свои коммиты, то вы
можете сделать это (сейчас или позже) вызвав команду checkout снова,
но с параметром -b. Например:
git checkout -b <имя-новой-ветки>
HEAD сейчас на b66f9c7 Второй коммит
Теперь HEAD
указывает на второй коммит b66f9c7
, и наш репозиторий вернулся к состоянию на момент второго коммита. Проверим содержимое директории:
$ ls
file.txt new-file.txt
Файла three-file.txt
ещё нет, так как он был добавлен позже, в третьем коммите.
Посмотрим историю коммитов:
$ git log
Вывод:
commit b66f9c79eb37de0beb099b04cc7f7f4979a98a83 (HEAD)
Author: uPagge
Date: Tue Jun 15 21:50:01 2021 +0300
Второй коммит
commit 06f7fc0a337bb6105f6a792fcc93eb2dda2a4dda
Author: uPagge
Date: Tue Jun 15 21:48:54 2021 +0300
Первый коммит
Мы откатились к состоянию второго коммита, но третий коммит и изменения в нём не потерялись. Чтобы это проверить, используем команду git log --all
, которая покажет все коммиты, включая те, которые находятся не в текущей ветке:
$ git log --all
Вывод:
commit b64191a1a53c7f28429042c1fe00702c15354fab (master)
Author: uPagge
Date: Tue Jun 15 21:52:20 2021 +0300
Третий коммит
commit b66f9c79eb37de0beb099b04cc7f7f4979a98a83 (HEAD)
Author: uPagge
Date: Tue Jun 15 21:50:01 2021 +0300
Второй коммит
commit 06f7fc0a337bb6105f6a792fcc93eb2dda2a4dda
Author: uPagge
Date: Tue Jun 15 21:48:54 2021 +0300
Первый коммит
Теперь вернём HEAD
обратно на ветку main
:
$ git checkout main
Git сообщит:
Предыдущая позиция HEAD была b66f9c7 Второй коммит
Переключено на ветку «main»
Проверим содержимое директории:
$ ls
file.txt new-file.txt three-file.txt
Теперь всё в порядке, и файл three-file.txt
, добавленный в третьем коммите, снова на месте.
Относительные ссылки
Перемещаться по коммитам с помощью указания их хэшей может быть неудобно. Поэтому Git поддерживает относительные ссылки, которые позволяют начинать с удобного места и двигаться от него.
Относительные ссылки — это мощный инструмент, но мы рассмотрим два простых способа их использования:
- Перемещение на один коммит назад:
^
- Перемещение на несколько коммитов назад:
~<num>
Оператор каретки
Когда мы добавляем каретку ^ к имени указателя (например, к ветке main
), Git воспринимает это как команду найти родительский коммит. Например:
main^
означает “первый родитель ветки main”.main^^
— это “родитель родителя”, то есть “прародитель” ветки main.
Давайте переключимся на коммит, который находится на один шаг назад от текущего коммита на ветке main
:
$ git checkout main^
Git выведет:
Примечание: переход на «main^».
...
HEAD сейчас на b66f9c7 Второй коммит
Теперь HEAD
указывает на второй коммит, который является родителем третьего коммита. То есть мы успешно переместились назад по истории коммитов.
main
остался на третьем коммите, а мы просто сдвинули HEAD
. Если вы снова выполните команду git checkout main^
, это переместит вас на родителя третьего коммита, что не изменится.Чтобы переместиться ещё дальше, можно использовать относительную ссылку через HEAD
. Например, чтобы перейти на первый коммит, воспользуемся командой:
$ git checkout HEAD^
Git выведет:
Предыдущая позиция HEAD была b66f9c7 Второй коммит
HEAD сейчас на 06f7fc0 Первый коммит
Теперь HEAD
указывает на первый коммит, и вы откатились на самый ранний этап проекта.
Чтобы вернуться на третий коммит, достаточно вернуться на ветку main
:
$ git checkout main
Git покажет:
Предыдущая позиция HEAD была 06f7fc0 Первый коммит
Переключено на ветку «main»
Теперь мы снова на третьем коммите, и HEAD
вернулся к текущему состоянию ветки main
.
Оператор ~
Предположим, что вам нужно переместиться на несколько шагов назад по истории коммитов. Печатать несколько кареток ^
может быть неудобно, поэтому Git поддерживает также оператор тильда ~
. С помощью этого оператора можно указать, на сколько шагов назад вы хотите переместиться.
Дополнительно к тильде можно добавить количество родительских коммитов, через которые необходимо пройти. Посмотрим, как это работает:
$ git checkout HEAD~2
Git выведет:
Примечание: переход на «HEAD~2».
...
HEAD сейчас на 06f7fc0 Первый коммит
Таким образом, мы переместились на два коммита назад и теперь находимся на первом коммите. Чтобы вернуться обратно на ветку main
, просто выполните:
$ git checkout main
Заключение
Это была первая статья по обучению Git. Мы установили Git и научились работать с основными командами для создания коммитов и навигации по ним. Этих знаний уже достаточно, чтобы работать с Git локально и не потерять ваши изменения. О других командах вы можете узнать в следующих статьях.
Если всё получилось, переходите к следующему уроку 👇