## Работа с ветками Вернёмся к нашему локальному репозиторию, в котором сейчас два коммита. До этого момента наша история представляла собой **одну линию коммитов**, где каждый новый коммит просто добавлялся к предыдущему. Но иногда возникает необходимость **работать параллельно над несколькими задачами или фичами** в одном репозитории. Один из вариантов — скопировать папку и работать отдельно. Но в Git есть гораздо лучшее решение — **ветки**. Поскольку Git использует древовидную структуру для хранения коммитов, мы можем создавать ветки для работы над разными задачами независимо друг от друга. Из одного коммита можно создать несколько веток, и их потом можно будет объединить (merge). Работая с ветками: * могут существовать **несколько линий истории**; * можно "перескакивать" между ними через `checkout`; * при переходе между ветками Git будет **заменять содержимое рабочей директории** на соответствующую выбранной версии. Давайте создадим новую ветку: ``` $ git branch b1 $ git log --oneline --graph * 7f3b00e (HEAD -> master, b1) adding file 2 * df2fb7a adding file 1 ``` Мы создали ветку **b1**. Из вывода видно, что **b1** тоже указывает на последний коммит (`7f3b00e`), но `HEAD` пока всё ещё указывает на ветку **master**. **Напоминание:** HEAD указывает туда, где мы находимся сейчас. *** Переключаемся на ветку b1: ``` $ git checkout b1 Switched to branch 'b1' $ git log --oneline --graph * 7f3b00e (HEAD -> b1, master) adding file 2 * df2fb7a adding file 1 ``` Теперь `HEAD` указывает на ветку **b1**. Ветки **master** и **b1** пока обе ссылаются на один и тот же коммит (`7f3b00e`). *** Делаем новый коммит в ветке b1: ``` # Создаём новый файл $ echo "I am a file in b1 branch" > b1.txt # Добавляем его в индекс $ git add b1.txt # Делаем коммит $ git commit -m "adding b1 file" [b1 872a38f] adding b1 file 1 file changed, 1 insertion(+) create mode 100644 b1.txt ``` Проверяем историю: ``` $ git log --oneline --graph * 872a38f (HEAD -> b1) adding b1 file * 7f3b00e (master) adding file 2 * df2fb7a adding file 1 ``` Теперь видно, что ветка **b1** пошла своим путём от общего коммита `7f3b00e`. *** Теперь переключимся обратно на ветку master: ``` $ git checkout master Switched to branch 'master' ``` И сделаем новый коммит в ветке **master**: ``` $ echo "new file in master branch" > master.txt $ git add master.txt $ git commit -m "adding master.txt file" [master 60dc441] adding master.txt file 1 file changed, 1 insertion(+) create mode 100644 master.txt ``` Проверяем историю в master: ``` $ git log --oneline --graph * 60dc441 (HEAD -> master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 ``` Обратите внимание, ветка **b1** тут не отображается — мы сейчас находимся на **master**. *** Чтобы увидеть всю историю сразу: ``` $ git log --oneline --graph --all * 60dc441 (HEAD -> master) adding master.txt file | * 872a38f (b1) adding b1 file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 ``` На графике видно, как от коммита `7f3b00e` расходятся две разные ветви: **master** и **b1**. Обе ветки теперь могут развиваться независимо. Краткое резюме: * Git строит дерево коммитов. * Ветки (например, `master`, `b1`) — это просто **читаемые имена**, которые указывают на определённые коммиты в этом дереве. * Команды Git позволяют работать с этим деревом и ссылками на коммиты. * Содержимое рабочей папки меняется в зависимости от того, на какой версии мы находимся. ## Слияния (Merges) Допустим, работа над фичей в ветке **b1** завершена, и теперь нужно слить её в ветку **master**, где собирается финальная версия кода. Что для этого нужно сделать: 1. Переключиться на ветку **master**: ``` $ git checkout master ``` 1. Получить последние изменения из удалённого репозитория (например, с GitHub) — команда `git pull` (здесь пока это не показано). 2. Выполнить слияние ветки **b1** в **master**. Текущее состояние истории: ``` $ git log --oneline --graph --all * 60dc441 (HEAD -> master) adding master.txt file | * 872a38f (b1) adding b1 file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 ``` ### **Вариант 1: Обычное слияние (Merge)** Прямое слияние ветки **b1** в **master** создаст **новый коммит слияния**. ``` $ git merge b1 Merge made by the 'recursive' strategy. b1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 b1.txt ``` Теперь история выглядит так: ``` $ git log --oneline --graph --all * 8fc28f9 (HEAD -> master) Merge branch 'b1' |\ | * 872a38f (b1) adding b1 file * | 60dc441 adding master.txt file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 ``` ✅ Создан новый **коммит слияния** `8fc28f9`. **Минус:** Если веток будет много, то таких merge-коммитов тоже будет много, и история репозитория станет "загромождённой" и неудобной для чтения. **Откатим слияние, чтобы попробовать другой способ:** ``` $ git reset --hard 60dc441 HEAD is now at 60dc441 adding master.txt file ``` История вернулась к состоянию до слияния: ``` $ git log --oneline --graph --all * 60dc441 (HEAD -> master) adding master.txt file | * 872a38f (b1) adding b1 file |/ * 7f3b00e adding file 2 * df2fb7a adding file 1 ``` ### **Вариант 2: Перебазирование (Rebase)** Вместо слияния двух веток можно использовать **rebase**: перенести коммиты ветки **b1** "поверх" последнего коммита в **master**. Шаги: Переключаемся на ветку **b1**: ``` $ git checkout b1 Switched to branch 'b1' ``` Выполняем **rebase**: ``` $ git rebase master First, rewinding head to replay your work on top of it... Applying: adding b1 file ``` Что получилось: ``` $ git log --oneline --graph --all * 5372c8f (HEAD -> b1) adding b1 file * 60dc441 (master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 ``` **Что произошло:** * Ранее коммит `872a38f` в ветке **b1** "опирался" на `7f3b00e`. * После **rebase** он "переехал" на новый базовый коммит `60dc441` (из master). * История стала **прямой линией** без лишних merge-коммитов. **Завершаем процесс: сливаем b1 в master** Переключаемся обратно на **master**: ``` $ git checkout master Switched to branch 'master' ``` Проверяем историю: ``` $ git log --oneline --graph --all * 5372c8f (b1) adding b1 file * 60dc441 (HEAD -> master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 ``` Ветка **b1** уже основана на коммите из **master** — теперь можно просто сделать быстрое слияние (**fast-forward**): ``` $ git merge b1 Updating 60dc441..5372c8f Fast-forward b1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 b1.txt ``` *** **Финальная история:** ``` $ git log --oneline --graph --all * 5372c8f (HEAD -> master, b1) adding b1 file * 60dc441 adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 ``` ✅ Теперь обе ветки (`master` и `b1`) указывают на один и тот же коммит. ✅ Код из **b1** успешно слит в **master**. ✅ История чистая и прямая!