Git
Chapters ▾ 2nd Edition

3.1 Ветвление в Git - О ветвлении в двух словах

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

Некоторые люди, говоря о модели ветвления Git, называют ее “киллер-фича”, что выгодно выделяет Git на фоне остальных СКВ. Что в ней такого особенного? Ветвление Git очень легковесно. Операция создания ветки выполняется почти мгновенно, переключение между ветками туда-сюда, обычно, также быстро. В отличии от многих других СКВ, Git поощряет процесс работы, при котором ветвление и слияние выполняется часто, даже по несколько раз в день. Понимание и владение этой функциональностью дает Вам уникальный и мощный инструмент, который может полностью изменить привычный Вам процесс разработки.

О ветвлении в двух словах

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

Как вы можете помнить из Введение, Git не хранит данные в виде последовательности изменений, он использует набор снимков (snapshot).

Когда вы делаете коммит, Git сохраняет его в виде объекта, который содержит указатель на снимок (snapshot) подготовленных данных. Этот объект так же содержит имя автора и email, сообщение и указатель на коммит или коммиты непосредственно предшествующие данному (его родителей): отсутствие родителя для первоначального коммита, один родитель для обычного коммита, и несколько родителей для результатов слияния веток.

Представьте себе каталог, который содержит дерево файлов, и вы подготавливаете их все вместе, а затем сохраняете в виде одного коммита. В процессе подготовки вычисляется контрольная сумма каждого файла (SHA-1 как мы узнали из Введение), хранящая версию файла в репозитории Git (Git ссылается на них), затем эти контрольные суммы добавляются в область подготовленных файлов:

$ git add README test.rb LICENSE
$ git commit -m 'initial commit of my project'

Когда вы создаете коммит командой git commit, Git вычисляет контрольные суммы каждого подкаталога (в нашем случае, только основной каталог проекта) и сохраняет эти объекты дерева в репозитории. Затем Git создает объект коммита с метаданными и указателем на основное дерево проекта для возможности воссоздать этот снимок (snapshot) в случае необходимости.

Ваш репозиторий Git теперь хранит пять объектов: блоб (blob) для содержимого каждого файла, содержимое каталога в виде дерева с указателями на блобы сохраненных фалов, сам коммит с указателем на основное дерево, метаданные коммита.

Коммит и его дерево.
Рисунок 9. Коммит и его дерево

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

Коммит и его родители.
Рисунок 10. Коммит и его родители

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

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

Note

Ветка “master” в Git — это не специальная ветка. Она точно такая же, как и все остальные ветки. Она существует почти во всех репозиториях только лишь потому, что ее создает команда git init, а большинство людей не меняют ее название.

Ветка и история коммитов.
Рисунок 11. Ветка и история коммитов

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

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

$ git branch testing

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

Две ветки указывают на одну и ту же последовательность коммитов.
Рисунок 12. Две ветки указывают на одну и ту же последовательность коммитов

Как Git определяет, в какой ветке вы находитесь? Он хранит специальный указатель HEAD. Имейте ввиду, что в Git концепция HEAD значительно отличается от других систем контроля версий, которые вы могли использовать раньше (Subversion или CVS). В Git это указатель на локальную ветку, в которой вы находитесь. В нашем случае мы все еще находимся в ветке “master”. Команда git branch только создает новую ветку. Переключения не происходит.

HEAD указывает на ветку.
Рисунок 13. HEAD указывает на ветку

Вы можете легко это увидеть при помощи простой команды git log. Она покажет вам, куда указывают указатели веток. Эта опция называется --decorate.

$ git log --oneline --decorate
f30ab (HEAD, master, testing) add feature #32 - ability to add new
34ac2 fixed bug #1328 - stack overflow under certain conditions
98ca9 initial commit of my project

Видны ветки “master” и “testing”, которые указывают на коммит f30ab.

Переключение веток

Чтобы переключиться на существующую ветку, выполните команду git checkout. Давайте переключимся на ветку “testing”:

$ git checkout testing

В результате указатель HEAD переместится на ветку testing.

HEAD указывает на текущую ветку.
Рисунок 14. HEAD указывает на текущую ветку

Какой в этом смысл? Давайте сделаем еще один коммит:

$ vim test.rb
$ git commit -a -m 'made a change'
Указатель на ветку HEAD переместился вперед после коммита.
Рисунок 15. Указатель на ветку HEAD переместился вперед после коммита

Это интересно, потому что указатель на вашу ветку “testing” переместился вперед, а “master” все еще указывает на тот коммит, где вы были в момент выполнения команды git checkout для переключения веток. Давайте переключимся назад на ветку “master”:

$ git checkout master
HEAD перемещается
Рисунок 16. HEAD перемещается когда вы делаете checkout

Эта команда сделала две вещи. Она переместила указатель HEAD назад на ветку “master” и вернула файлы в рабочем каталоге в то состояние, которое было сохранено в снимке (snapshot), на который указывает ветка. Это также означает, что все изменения, вносимые с этого момента, будут отнесены к старой версии проекта. Другими словами, откатилась вся работа, выполненная в ветке “testing”, а вы можете продолжать в другом направлении.

Note
Переключение веток меняет файлы в рабочем каталоге

Важно запомнить, что когда вы переключаете ветки в Git, файлы в рабочем каталоге меняются. Если вы переключаетесь на старую ветку, то рабочий каталог будет выглядеть так же, как выглядел на момент последнего коммита в ту ветку. Если Git по каким-то причинам не может этого сделать — он не позволит вам переключиться.

Давайте сделаем еще несколько изменений и очередной коммит:

$ vim test.rb
$ git commit -a -m 'made other changes'

Теперь история вашего проекта разделилась (см Разветвленная история). Вы создали ветку, переключились в нее, поработали, а затем вернулись в основную ветку и поработали в ней. Эти изменения изолированы друг от друга: вы можете свободно переключаться туда и обратно, а когда будете готовы — слить их вместе. И все это делается простыми командами: branch, checkout и commit.

Разветвленная история.
Рисунок 17. Разветвленная история

Все это вы можете увидеть при помощи команды git log. Команда git log --oneline --decorate --graph --all выдаст историю ваших коммитов и покажет, где находятся указатели ваших веток, и как ветвилась история проекта.

$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
* f30ab add feature #32 - ability to add new formats to the
* 34ac2 fixed bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project

Cоздание и удаление веток совершенно не затратно, так как ветка в Git — это всего лишь файл, содержащий 40 символов контрольной суммы SHA-1 того коммита, на который он указывает. Создание новой ветки совершенно быстро и просто — это всего лишь запись 41 байта в файл (40 знаков и перевод строки).

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

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