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

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

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

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

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

Как вы можете помнить из [Что такое Git?](https://git-scm.com/book/ru/v2/ch00/what_is_git_section), Git не хранит данные в виде последовательности изменений, он использует набор снимков (snapshot).

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

Предположим, у вас есть каталог с тремя файлами и вы добавляете их все в индекс и создаёте коммит. Во время индексации вычисляется контрольная сумма каждого файла (SHA-1 как мы узнали из [Что такое Git?](https://git-scm.com/book/ru/v2/ch00/what_is_git_section)), затем каждый файл сохраняется в репозиторий (Git называет такой файл *блоб* — большой бинарный объект), а контрольная сумма попадёт в индекс:

```console
$ git add README test.rb LICENSE
$ git commit -m 'Initial commit'
```

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

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

<div class="imageblock" id="bkmrk-%D0%A0%D0%B8%D1%81%D1%83%D0%BD%D0%BE%D0%BA-9.-%D0%9A%D0%BE%D0%BC%D0%BC%D0%B8%D1%82-%D0%B8-"><div class="content">![Коммит и его дерево](https://git-scm.com/book/en/v2/images/commit-and-tree.png)</div><div class="title">Рисунок 9. Коммит и его дерево</div></div>Если вы сделаете изменения и создадите ещё один коммит, то он будет содержать указатель на предыдущий коммит.

<div class="imageblock" id="bkmrk-%D0%A0%D0%B8%D1%81%D1%83%D0%BD%D0%BE%D0%BA-10.-%D0%9A%D0%BE%D0%BC%D0%BC%D0%B8%D1%82-%D0%B8"><div class="content">![Коммит и его родители](https://git-scm.com/book/en/v2/images/commits-and-parents.png)</div><div class="title">Рисунок 10. Коммит и его родители</div></div>Ветка в Git — это простой перемещаемый указатель на один из таких коммитов. По умолчанию, имя основной ветки в Git — `master`. Как только вы начнёте создавать коммиты, ветка `master` будет всегда указывать на последний коммит. Каждый раз при создании коммита указатель ветки `master` будет передвигаться на следующий коммит автоматически.

<div class="admonitionblock note" id="bkmrk-%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%87%D0%B0%D0%BD%D0%B8%D0%B5-%D0%92%D0%B5%D1%82%D0%BA%D0%B0-%C2%ABma"><table><tbody><tr><td class="icon"><div class="title">Примечание</div></td><td class="content">Ветка «master» в Git — это не какая-то особенная ветка. Она точно такая же, как и все остальные ветки. Она существует почти во всех репозиториях только лишь потому, что её создаёт команда `git init`, а большинство людей не меняют её название.

</td></tr></tbody></table>

</div><div class="imageblock" id="bkmrk-%D0%A0%D0%B8%D1%81%D1%83%D0%BD%D0%BE%D0%BA-11.-%D0%92%D0%B5%D1%82%D0%BA%D0%B0-%D0%B8-"><div class="content">![Ветка и история коммитов](https://git-scm.com/book/en/v2/images/branch-and-history.png)</div><div class="title">Рисунок 11. Ветка и история коммитов</div></div>### Создание новой ветки

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

<div class="sect3" id="bkmrk-"><div class="paragraph">  
</div><div class="listingblock"><div class="content"></div></div></div>```console
$ git branch testing
```

<div class="sect3" id="bkmrk--1"><div class="listingblock"><div class="content"></div></div><div class="paragraph">  
</div></div>В результате создаётся новый указатель на текущий коммит.

<div class="sect3" id="bkmrk-%D0%A0%D0%B8%D1%81%D1%83%D0%BD%D0%BE%D0%BA-12.-%D0%94%D0%B2%D0%B5-%D0%B2%D0%B5%D1%82%D0%BA"><div class="paragraph">  
</div><div class="imageblock"><div class="content">![Две ветки указывают на одну и ту же последовательность коммитов](https://git-scm.com/book/en/v2/images/two-branches.png)</div><div class="title">Рисунок 12. Две ветки указывают на одну и ту же последовательность коммитов</div></div><div class="paragraph">  
</div></div>Как Git определяет, в какой ветке вы находитесь? Он хранит специальный указатель `HEAD`. Имейте ввиду, что в Git концепция `HEAD` значительно отличается от других систем контроля версий, которые вы могли использовать раньше (Subversion или CVS). В Git — это указатель на текущую локальную ветку. В нашем случае мы всё ещё находимся в ветке `master`. Команда `git branch` только *создаёт* новую ветку, но не переключает на неё.

<div class="sect3" id="bkmrk-%D0%A0%D0%B8%D1%81%D1%83%D0%BD%D0%BE%D0%BA-13.-head-%D1%83%D0%BA%D0%B0"><div class="paragraph">  
</div><div class="imageblock"><div class="content">![HEAD указывает на ветку](https://git-scm.com/book/en/v2/images/head-to-master.png)</div><div class="title">Рисунок 13. HEAD указывает на ветку</div></div><div class="paragraph">  
</div></div>Вы можете легко это увидеть при помощи простой команды `git log`, которая покажет вам куда указывают указатели веток. Эта опция называется `--decorate`.

<div class="sect3" id="bkmrk--2"><div class="paragraph">  
</div><div class="listingblock"><div class="content"></div></div></div>```console
$ git log --oneline --decorate
f30ab (HEAD -> master, testing) Add feature #32 - ability to add new formats to the central interface
34ac2 Fix bug #1328 - stack overflow under certain conditions
98ca9 Initial commit
```

<div class="sect3" id="bkmrk--3"><div class="listingblock"><div class="content"></div></div><div class="paragraph">  
</div></div>Здесь можно увидеть указывающие на коммит `f30ab` ветки: `master` и `testing`.

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

Для переключения на существующую ветку выполните команду `git checkout`. Давайте переключимся на ветку `testing`:

<div class="sect3" id="bkmrk--4"><div class="paragraph">  
</div><div class="listingblock"><div class="content"></div></div></div>```console
$ git checkout testing
```

<div class="sect3" id="bkmrk--5"><div class="listingblock"><div class="content"></div></div><div class="paragraph">  
</div></div>В результате указатель `HEAD` переместится на ветку `testing`.

<div class="sect3" id="bkmrk-%D0%A0%D0%B8%D1%81%D1%83%D0%BD%D0%BE%D0%BA-14.-head-%D1%83%D0%BA%D0%B0"><div class="paragraph">  
</div><div class="imageblock"><div class="content">![HEAD указывает на текущую ветку](https://git-scm.com/book/en/v2/images/head-to-testing.png)</div><div class="title">Рисунок 14. HEAD указывает на текущую ветку</div></div><div class="paragraph">  
</div></div>Какой в этом смысл? Давайте сделаем ещё один коммит:

<div class="sect3" id="bkmrk--6"><div class="paragraph">  
</div><div class="listingblock"><div class="content"></div></div></div>```console
$ vim test.rb
$ git commit -a -m 'made a change'
```

<div class="sect3" id="bkmrk-%D0%A0%D0%B8%D1%81%D1%83%D0%BD%D0%BE%D0%BA-15.-%D0%A3%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB"><div class="listingblock"><div class="content"></div></div><div class="imageblock"><div class="content">![Указатель на ветку HEAD переместился вперёд после коммита](https://git-scm.com/book/en/v2/images/advance-testing.png)</div><div class="title">Рисунок 15. Указатель на ветку HEAD переместился вперёд после коммита</div></div><div class="paragraph">  
</div></div>Интересная ситуация: указатель на ветку `testing` переместился вперёд, а `master` указывает на тот же коммит, где вы были до переключения веток командой `git checkout`. Давайте переключимся назад на ветку `master`:

<div class="sect3" id="bkmrk--7"><div class="paragraph">  
</div><div class="listingblock"><div class="content"></div></div></div>```console
$ git checkout master
```

<div class="sect3" id="bkmrk-%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%87%D0%B0%D0%BD%D0%B8%D0%B5-git-log-%D0%BD"><div class="listingblock"><div class="content"></div></div><div class="admonitionblock note"><table><tbody><tr><td class="icon"><div class="title">Примечание</div></td><td class="content"><div class="title">`git log` не показывает все ветки по умолчанию</div>Если выполнить команду `git log` прямо сейчас, то в её выводе только что созданная ветка «testing» фигурировать не будет.

Ветка никуда не исчезла; просто Git не знает, что именно она вас интересует, и выводит наиболее полезную по его мнению информацию. Другими словами, по умолчанию `git log` отобразит историю коммитов только для текущей ветки.

Для просмотра истории коммитов другой ветки необходимо явно указать её имя: `git log testing` Чтобы посмотреть историю по всем веткам — выполните команду с дополнительным флагом: `git log --all`.

</td></tr></tbody></table>

</div><div class="imageblock"><div class="content">![HEAD перемещается когда вы делаете checkout](https://git-scm.com/book/en/v2/images/checkout-master.png)</div><div class="title">Рисунок 16. HEAD перемещается когда вы делаете checkout</div></div><div class="paragraph">  
</div></div>Эта команда сделала две вещи: переместила указатель `HEAD` назад на ветку `master` и вернула файлы в рабочем каталоге в то состояние, на снимок которого указывает `master`. Это также означает, что все вносимые с этого момента изменения будут относиться к старой версии проекта. Другими словами, вы откатили все изменения ветки `testing` и можете продолжать в другом направлении.

<div class="sect3" id="bkmrk-%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%87%D0%B0%D0%BD%D0%B8%D0%B5-%D0%9F%D0%B5%D1%80%D0%B5%D0%BA%D0%BB%D1%8E%D1%87%D0%B5"><div class="paragraph">  
</div><div class="admonitionblock note"><table><tbody><tr><td class="icon"><div class="title">Примечание</div></td><td class="content"><div class="title">Переключение веток меняет файлы в рабочем каталоге</div>Важно запомнить, что при переключении веток в Git происходит изменение файлов в рабочем каталоге. Если вы переключаетесь на старую ветку, то рабочий каталог будет выглядеть так же, как выглядел на момент последнего коммита в ту ветку. Если Git по каким-то причинам не может этого сделать — он не позволит вам переключиться вообще.

</td></tr></tbody></table>

</div><div class="paragraph">  
</div></div>Давайте сделаем ещё несколько изменений и создадим очередной коммит:

<div class="sect3" id="bkmrk--8"><div class="paragraph">  
</div><div class="listingblock"><div class="content"></div></div></div>```console
$ vim test.rb
$ git commit -a -m 'made other changes'
```

<div class="sect3" id="bkmrk--9"><div class="listingblock"><div class="content"></div></div><div class="paragraph">  
</div></div>Теперь история вашего проекта разошлась (см [Разветвлённая история](https://git-scm.com/book/ru/v2/ch00/rdivergent_history)). Вы создали ветку и переключились на неё, поработали, а затем вернулись в основную ветку и поработали в ней. Эти изменения изолированы друг от друга: вы можете свободно переключаться туда и обратно, а когда понадобится — объединить их. И всё это делается простыми командами: `branch`, `checkout` и `commit`.

<div class="sect3" id="bkmrk-%D0%A0%D0%B8%D1%81%D1%83%D0%BD%D0%BE%D0%BA-17.-%D0%A0%D0%B0%D0%B7%D0%B2%D0%B5%D1%82%D0%B2%D0%BB"><div class="paragraph">  
</div><div class="imageblock" id="bkmrk-%D0%A0%D0%B8%D1%81%D1%83%D0%BD%D0%BE%D0%BA-17.-%D0%A0%D0%B0%D0%B7%D0%B2%D0%B5%D1%82%D0%B2%D0%BB-1"><div class="content">![Разветвлённая история](https://git-scm.com/book/en/v2/images/advance-master.png)</div><div class="title">Рисунок 17. Разветвлённая история</div></div><div class="paragraph">  
</div></div>Все описанные действия можно визуализировать с помощью команды `git log`. Для отображения истории коммитов, текущего положения указателей веток и истории ветвления выполните команду `git log --oneline --decorate --graph --all`.

<div class="sect3" id="bkmrk--10"><div class="paragraph">  
</div><div class="listingblock"><div class="content"></div></div></div>```console
$ 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 central interface
* 34ac2 Fix bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project
```

<div class="sect3" id="bkmrk--11"><div class="listingblock"><div class="content"></div></div><div class="paragraph">  
</div></div>Ветка в Git — это простой файл, содержащий 40 символов контрольной суммы SHA-1 коммита, на который она указывает; поэтому операции с ветками являются дешёвыми с точки зрения потребления ресурсов или времени. Создание новой ветки в Git происходит так же быстро и просто как запись 41 байта в файл (40 знаков и перевод строки).

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

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

<div class="sect3" id="bkmrk-%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%87%D0%B0%D0%BD%D0%B8%D0%B5-%D0%9E%D0%B4%D0%BD%D0%BE%D0%B2%D1%80%D0%B5%D0%BC%D0%B5"><div class="paragraph">  
</div><div class="admonitionblock note"><table><tbody><tr><td class="icon"><div class="title">Примечание</div></td><td class="content"><div class="title">Одновременное создание новой ветки и переключение на неё</div>Как правило, при создании новой ветки вы хотите сразу на неё переключиться — это можно сделать используя команду `git checkout -b <newbranchname>`.

</td></tr></tbody></table>

</div><div class="admonitionblock note"><table><tbody><tr><td class="icon"><div class="title">Примечание</div></td><td class="content">Начиная с Git версии 2.23, вы можете использовать `git switch` вместо `git checkout`, чтобы:

<div class="ulist">- Переключиться на существующую ветку: `git switch testing-branch`.
- Создать новую ветку и переключиться на неё: `git switch -c new-branch`. Флаг `-c` означает создание, но также можно использовать полный формат:` --create`.
- Вернуться к предыдущей извлечённой ветке: `git switch -`.

</div></td></tr></tbody></table>

</div></div>