Ветки в Git

Теоретическая часть

Если Вы впервые знакомитесь с системой Git, советуем сначала ознакомиться с лабораторной работой в файле git.rst, в которой Вы найдёте необходимый теоретический минимум для понимания Git. Здесь мы сфокусируем наше внимание на ветках (branches) в Git и работе с ними.

Для начала ознакомимся с основными терминами.

Указатель – это ссылка на определенный коммит или ветку. А ссылка – это некоторая метка, которую использует Git или сам пользователь, чтобы указать на коммит или ветку. Давайте приведем примеры еще некоторых часто используемых указателей:

  • HEAD – так называемый курсор Git. Главное назначение HEAD - определять, в каком состоянии находится рабочая копия (напомним, что рабочая копия – это все файлы репозитория, за исключением директории .git/). На какой коммит указывает HEAD – из того коммита и загружаются файлы в рабочую директорию.
  • ORIG_HEAD – указатель, который появляется, когда мы вручную передвигаем HEAD на какой-нибудь НЕ последний коммит. ORIG_HEAD указывает на тот же коммит, на который указывал HEAD до передвижения назад. Нужен он, чтобы мы имели возможность вернуться на хронологически последний коммит без существенных затрат (в истории мы не будем видеть все коммиты старше нашего, а поэтому не сможем узнать хэш последнего).
  • Пользовательские указатели. Пользователи сами могут создавать указатели. Например, вы можете создать указатель version-1.2.1, который будет указывать на коммит, в котором хранится версия 1.2.1 вашего проекта. Это довольно удобно, поскольку вы можете переключаться на коммит с той или иной версией, не запоминая его хэш.

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

Альтернативный текст

Структура веток в Git

Для чего нужны ветки?

  1. Ветки нужны, чтобы несколько программистов могли вести работу над одним и тем же проектом или даже файлом одновременно, при этом не мешая друг другу.
  2. Кроме того, ветки используются для тестирования экспериментальных функций: чтобы не повредить основному проекту, создается новая ветка специально для экспериментов. Если эксперимент удался, изменения с экспериментальной ветки переносятся на основную, если нет – новая ветка попросту удаляется, а проект остается нетронутым.
  3. Помимо прочего, ветки можно использовать для разных выходящих параллельно релизов одного проекта. Например, в репозитории Python может быть две ветки: python-2 и python-3. До закрытия python-2 релизы этих версий языка выходили независимо друг от друга, поэтому могло иметь место такое разделение на ветки.

Создание и удаление ветки, git branch:

Что же на самом деле происходит при создании ветки? Всего лишь создаётся новый указатель для дальнейшего перемещения. Допустим вы хотите создать новую ветку с именем "test". Вы можете это сделать командой git branch :

git branch test #будет создана ветка с именем "test"
  • Используйте для имени латинские буквы. Тут есть одно замечание. Когда мы создали ветку с некоторым именем, текущей осталась ветка, которая была выделена до этого. Например, master. И если после создания ветки мы скажем git commit, то будет продолжена ветка master. Непонимание этого часто приводит к ошибкам.
  • Чтобы продолжить новую ветку нужно её создать, потом переключиться на неё и сделать commit.

1. Создаем ветку:

git branch feature #создание ветки с именем "feature" локально

2. Чтобы переключиться на созданную ветку, воспользуемся командой git checkout <name>:

git checkout feature#выбор ветки с именем "feature" локально

3. Делаем commit:

git commit

Теперь у нас есть вторая ветка с именем feature.

Чтобы удалить ветку с именем name, необходимо к команде git branch приписать ключ -d <name>:

git branch -d <name> # удалить локальную ветку с именем name

Однако, если данная ветка не слита полностью с какой-то другой (о слиянии веток говорится ниже), то Git не удалит ветку и выдаст предупреждение. Чтобы игнорировать его, нужно ввести ключ -D:

git branch -D <name> # удалить ветку, игнорируя предупреждение Git

Также стоит заметить, что команда git branch по умолчанию выводит список локальных веток. С ключами -r, -a можно вывести, соответственно, либо только удаленные ветки, либо все ветки. При выводе текущая ветка будет обозначена символом *.

$ git branch
  master
* feature

Переход между ветками, git checkout и git switch:

Для перехода между ветками служит уже известная вам команда git checkout <name>, перемещающаяя указатель HEAD на указанную ветку. Для перехода на предыдущую ветку удобно использовать команду git checkout -. Как правило, при создании новой ветки вы хотите сразу на неё переключиться — это можно сделать используя команду git checkout -b <newbranchname>.

Начиная с Git версии 2.23, вы можете использовать git switch вместо git checkout, чтобы:

  • Переключиться на существующую ветку: git switch testing-branch.
  • Создать новую ветку и переключиться на нее: git switch -c new-branch.
  • Вернуться к предыдущей извлечённой ветке: git switch -.

Слияние веток (merge).

Дадим определения:

  • Сливаемая ветка – та ветка, с которой мы берем изменения, чтобы влить их в целевую.
  • Целевая ветка – та ветка, в которую мы сливаем наши изменения.
  • Слияние веток – это перенос изменений с одной ветки на другую. При этом слияние не затрагивает сливаемую ветку, то есть она остается в том же состоянии, что позволяет нам потом продолжить

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

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

git merge <name>  #объединить текущую ветку с веткой name

git merge обладает следующими ключами:

имя ключа описание
--ff включить fast-forward, если это возможно
--no-ff отключить fast-forward
--ff-only остановить merge, если его невозможно сделать fast-forward
--abort Ключ, использующийся только при разрешении конфликтов. Позволяет прервать слияние и вернуть все к моменту начала операции
--continue Ключ, использующийся только при разрешении конфликтов. Позволяет продолжить слияние после разрешения всех конфликтов

По умолчанию исползуется ключ --ff.

Использованные в таблице термины будут описаны далее.

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

  • Стратегия слияния – это набор правил, которыми руководствуется Git при выполнении слияния.

Существует две основных стратегии слияния:

  1. Явное слияние
  2. Неявное слияние.

Их различие заключается в том, что при явном всегда создается новый коммит, а при неявном – используются существующие коммиты.

Явное слияние

Во время явного слияния создается так называемый merge-коммит. Основное предназначение этого коммита состоит в том, чтобы "соединить" изменения двух веток. У этого коммита есть одна особенность: два родительских коммита. Один родитель – последний коммит сливаемой ветки, второй – последний коммит целевой ветки.

Допустим, у нас есть граф вида:

Альтернативный текст

Перед слиянием

Выполним команду:

$ git checkout main
$ git merge --no-ff develop # --no-ff для явного слияния

Итак, git merge делает следующие шаги:

  1. Проверяет, нет ли конфликтов, т.е. не удалят и не перепишут ли наши изменения какую-либо уже существующую информацию. Если возникает конфликт git merge останавливается, чтобы получить инструкции от пользователя, но этот случай мы рассмотрим ниже. А пока допустим, что конфликтов нет.
  2. Добавляет все изменения из коммитов 3-5 в индекс ветки main
  3. Делает коммит

После git merge граф репозитория будет выглядеть следующим образом:

Альтернативный текст

После явного слияния

Неявное слияние

Во время неявного слияния не создается новых коммитов: используются только уже существующие. Суть этого слияния заключается в том, что из вливаемой ветки извлекаются несколько коммитов, а затем они применяются к последнему коммиту целевой ветки. Такое слияние называется fast-forward.

Выполним команду:

$ git checkout main
$ git merge # по дефолту выполнится -ff

Тогда git merge поступит так:

  1. Проверит, что в ветке main нет коммитов, сделанных после ответвления develop.
  2. Проверит, что не возникает конфликтов, если конфликты возникнут, Git попросит пользователя разрешить их.
  3. Перенесет указатель main на Commit-5. Теперь ветка develop как бы стала веткой main.

После слияния граф будет выглядеть таким образом:

Альтернативный текст

После неявного слияния

Как видно из рисунка, новый коммит действительно не был создан. Вместо него, Git "подставил" в ветку main уже существующие коммиты из ветки develop.

Стоит подробнее разобрать первый пункт в работе git merge. В нем говорится, что Git проверит, что в ветке main нет коммитов, после ответвления develop. Дело в том, что режим fast-forward возможен не всегда, например в случае такого репозитория:

Альтернативный текст

Слияние в режиме fast-forward выполнить будет невозможно, поскольку в таком случае мы потеряем всю информацию о Коммите-6. Не будет активных ссылок, указывающих на этот коммит, или одного из его наследников: последующих коммитов, для которых Коммит-6 стал родителем. Поэтому в данном случае придется выполнять явный git merge с созданием merge-коммита.

Для более детального понимания слияния веток рекомендуется самостоятельно изучить вопрос о конфликтах слияния и пути их решения, а также такие команды, как git rebase и git cherry-pick.

Просмотр состояния ветки, git status, git log, git diff.

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

  • Команда git status предоставляет информацию о состоянии ветки: незакоммиченных и неотслеживаемых файлах.
  • Команда git log выводит историю коммитов.
  • Команда git diff помогает просмотреть изменения между файлами, коммитами, ветками.

Практическая часть

Здесь предложено несколько задач для закрепления материала о ветвлениях в Git.

Итак:

  1. Создайте репозиторий в Git.
  2. Создайте файл squares.py и напишите класс (шаблон) для вычисления площадей различных фигур. Для начала напишите функцию вычисления площади круга.
  3. Закоммитте изменения (и не забывайте это делать в дальнейшем).
  4. Создайте новую ветку triangle и перейдите в неё.
  5. Допишите в Ваш класс функцию для вычисления площади треугольника, протестируйте её.
  6. После удачного тестирования слейте ветку triangle с основной.
  7. С помощью команды git log посмотрите историю Ваших коммитов.
  8. Спомощью команды git diff проследите изменения, которые претерпевал Ваш файл в процессе доработки.

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

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