Skip to content

Глава 14.1: Git глубже — branching, merge, rebase, pull requests, конфликты

🎯 Чему научимся

После этой главы ты сможешь:

  • Создавать и управлять ветками для разных фич и исправлений
  • Понимать разницу между merge и rebase и знать, когда что использовать
  • Разрешать конфликты слияния профессионально
  • Работать с pull requests в командной разработке
  • Использовать продвинутые Git-команды для управления историей

📖 Зачем нужны ветки?

Проблема без веток

Представь: ты работаешь над интернет-магазином. Тебе нужно:

  1. Добавить новую систему оплаты (неделя работы)
  2. Срочно исправить баг в корзине (1 час)
  3. Экспериментировать с новым дизайном

Если работать в одной ветке:

  • Недописанная оплата сломает проект
  • Нельзя выпустить исправление бага, не выпустив недоделанную оплату
  • Эксперименты могут сломать рабочий код

Решение: ветки

main (рабочая версия)
    ├── feature/payment-gateway (новая фича)
    ├── bugfix/cart-total (срочное исправление)
    └── experiment/redesign (эксперимент)

Каждая задача живёт в своей ветке, не мешая другим.


🌿 Работа с ветками

Создание и переключение

bash
# Посмотреть все ветки
git branch

# Создать новую ветку
git branch feature/user-profile

# Переключиться на ветку
git checkout feature/user-profile

# Создать и сразу переключиться (часто используется)
git checkout -b feature/user-profile

# Современный синтаксис (Git 2.23+)
git switch feature/user-profile
git switch -c feature/user-profile  # создать и переключиться

Практический пример: работа над фичей

bash
# Ты на main, начинаешь новую фичу
git checkout -b feature/add-comments

# Работаешь, делаешь коммиты
git add app/Http/Controllers/CommentController.php
git commit -m "Add CommentController with store method"

git add resources/views/comments/form.blade.php
git commit -m "Add comment form view"

# Посмотреть, где ты сейчас
git branch
# * feature/add-comments  <- звёздочка показывает текущую ветку
#   main

# Посмотреть историю
git log --oneline
# abc123 Add comment form view
# def456 Add CommentController with store method
# ...

Именование веток: соглашения

bash
# ✅ Хорошие имена
feature/user-authentication
feature/payment-integration
bugfix/cart-calculation
hotfix/security-patch
experiment/new-ui
refactor/database-queries

# ❌ Плохие имена
new-stuff
fix
test
my-branch
asdf

Типы префиксов:

  • feature/ — новая функциональность
  • bugfix/ — исправление бага
  • hotfix/ — срочное исправление для production
  • refactor/ — рефакторинг без новых фич
  • experiment/ — эксперименты, может быть удалена
  • release/ — подготовка релиза

🔀 Merge: слияние веток

Как работает merge

main:        A---B---C
                  \
feature:           D---E---F

После git merge feature:

main:        A---B---C-------M
                  \         /
feature:           D---E---F

Merge создаёт merge commit (M), который объединяет изменения.

Fast-forward merge

Если main не изменялся:

main:        A---B---C
                      \
feature:               D---E---F

После git merge feature (fast-forward):

main:        A---B---C---D---E---F

История становится линейной, merge commit не создаётся.

Практика: слияние фичи

bash
# Закончили работу над комментариями
git checkout feature/add-comments
git add .
git commit -m "Add comment validation"

# Переключаемся на main
git checkout main

# Сливаем фичу в main
git merge feature/add-comments

# Если всё хорошо:
# Updating abc123..def456
# Fast-forward
#  app/Http/Controllers/CommentController.php | 45 +++++++++
#  resources/views/comments/form.blade.php    | 23 +++++
#  2 files changed, 68 insertions(+)

# Теперь можно удалить ветку фичи
git branch -d feature/add-comments

Опции merge

bash
# Всегда создавать merge commit (даже при fast-forward)
git merge --no-ff feature/add-comments

# Пример когда это полезно:
main:        A---B---C-------M
                  \         /
feature:           D---E---F

# Теперь видно, что D-E-F были одной фичей

🔧 Rebase: перебазирование

Как работает rebase

main:        A---B---C---D---E
                  \
feature:           F---G---H

После git rebase main:

main:        A---B---C---D---E
                              \
feature:                       F'---G'---H'

Rebase переписывает историю: коммиты F, G, H применяются заново поверх E.

Зачем нужен rebase?

Чистая линейная история:

bash
# С merge
*   Merge branch 'feature/comments'
|\
| * Add validation
| * Add form
* | Fix bug in posts
* | Update dependencies
|/
* Initial commit

# С rebase
* Add validation
* Add form
* Fix bug in posts
* Update dependencies
* Initial commit

Практика: rebase перед слиянием

bash
# Ты работаешь в feature/comments
git checkout feature/comments

# В main появились новые коммиты, хочешь их получить
git rebase main

# Git применяет твои коммиты поверх актуального main
# Если конфликтов нет:
# Successfully rebased and updated refs/heads/feature/comments.

# Теперь в main можно сделать fast-forward merge
git checkout main
git merge feature/comments  # будет fast-forward

Интерактивный rebase: редактирование истории

bash
# Посмотреть последние 3 коммита
git log --oneline -3
# abc123 Fix typo in comment
# def456 Add comment validation  
# ghi789 Add comment form

# Хочешь объединить "Fix typo" с "Add validation"
git rebase -i HEAD~3

# Откроется редактор:
pick ghi789 Add comment form
pick def456 Add comment validation
pick abc123 Fix typo in comment

# Изменяешь:
pick ghi789 Add comment form
pick def456 Add comment validation
fixup abc123 Fix typo in comment  # <- fixup объединит с предыдущим

# Сохраняешь, закрываешь редактор
# Теперь история:
# def456 Add comment validation (включает исправление опечатки)
# ghi789 Add comment form

Команды в интерактивном rebase:

  • pick — использовать коммит как есть
  • reword — изменить сообщение коммита
  • edit — остановиться для изменения коммита
  • squash — объединить с предыдущим, сохранив оба сообщения
  • fixup — объединить с предыдущим, используя только его сообщение
  • drop — удалить коммит

⚔️ Merge vs Rebase: когда что использовать?

Merge — используй когда:

✅ Работаешь в публичной ветке (main, develop) ✅ Хочешь сохранить полную историю изменений ✅ Работаешь в команде и ветка уже запушена в удалённый репозиторий ✅ Важно видеть, когда фичи были влиты

bash
git checkout main
git merge feature/add-comments

Rebase — используй когда:

✅ Работаешь в локальной ветке, которую ещё не пушил ✅ Хочешь чистую линейную историю ✅ Синхронизируешь свою ветку с актуальным main ✅ Чистишь историю перед pull request

bash
git checkout feature/add-comments
git rebase main

⚠️ Золотое правило rebase

НИКОГДА не делай rebase публичных веток!

bash
# ❌ ОПАСНО! Не делай так:
git checkout main
git rebase feature/something  # Переписываешь main!

# Если main уже запушен, это сломает работу всей команды

Почему? Rebase переписывает историю. Если кто-то уже скачал старую версию main, у него будут конфликты.

Практический workflow

bash
# 1. Начинаешь новую фичу
git checkout -b feature/notifications

# 2. Работаешь, делаешь коммиты
git commit -m "Add notification model"
git commit -m "Add notification controller"

# 3. В main появились изменения, хочешь их получить
git fetch origin
git rebase origin/main  # ✅ Ок, твоя ветка локальная

# 4. Пушишь в первый раз
git push -u origin feature/notifications

# 5. Продолжаешь работу, делаешь коммиты
git commit -m "Add notification view"

# 6. Снова синхронизируешься с main
git fetch origin
git rebase origin/main

# 7. Теперь нужно force push (потому что переписал историю)
git push --force-with-lease  # ✅ Безопаснее чем --force

# 8. Создаёшь Pull Request
# Команда ревьюит, мерджит в main через GitHub/GitLab

💥 Конфликты слияния

Как возникают конфликты

main:        A---B (изменил user.php строка 10)
                  \
feature:           C (тоже изменил user.php строка 10)

При merge или rebase: конфликт!

Пример конфликта

Файл app/Models/User.php после попытки merge:

php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
<<<<<<< HEAD
    protected $fillable = ['name', 'email', 'password', 'phone'];
=======
    protected $fillable = ['name', 'email', 'password', 'avatar'];
>>>>>>> feature/add-avatar

    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

Разбор:

  • <<<<<<< HEAD — твоя версия (куда мерджишь)
  • ======= — разделитель
  • >>>>>>> feature/add-avatar — версия из ветки

Разрешение конфликта

bash
# Git остановился на конфликте
git status
# On branch main
# You have unmerged paths.
#   (fix conflicts and run "git commit")
#
# Unmerged paths:
#   (use "git add <file>..." to mark resolution)
#         both modified:   app/Models/User.php

# 1. Открываешь файл, исправляешь вручную

Исправленная версия:

php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    // Оставляем оба поля
    protected $fillable = ['name', 'email', 'password', 'phone', 'avatar'];

    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}
bash
# 2. Отмечаешь конфликт как разрешённый
git add app/Models/User.php

# 3. Продолжаешь merge
git commit  # Для merge
# или
git rebase --continue  # Для rebase

# Если хочешь отменить весь процесс:
git merge --abort
# или
git rebase --abort

Инструменты для разрешения конфликтов

bash
# Запустить mergetool (визуальный редактор)
git mergetool

# Использовать VS Code (рекомендуется)
code .

# VS Code показывает кнопки:
# - Accept Current Change (твоя версия)
# - Accept Incoming Change (их версия)
# - Accept Both Changes (обе версии)
# - Compare Changes (сравнить)

Стратегии при конфликтах

bash
# При merge: выбрать их версию для конкретного файла
git checkout --theirs app/config/app.php
git add app/config/app.php

# При merge: выбрать свою версию
git checkout --ours app/config/database.php
git add app/config/database.php

# При rebase: ours и theirs наоборот!
# --ours = то, куда ребейзишься (main)
# --theirs = твоя ветка

🔄 Pull Requests (Merge Requests)

Что такое Pull Request

Pull Request (PR) — запрос на слияние твоей ветки в основную. Используется для:

  • Код-ревью перед мерджем
  • Обсуждения изменений
  • Автоматического тестирования
  • Документирования истории решений

Workflow с Pull Request

bash
# 1. Создаёшь ветку
git checkout -b feature/user-notifications

# 2. Работаешь, коммитишь
git add .
git commit -m "Add notification system"

# 3. Пушишь ветку
git push -u origin feature/user-notifications

# 4. На GitHub/GitLab создаёшь Pull Request
# feature/user-notifications -> main

# 5. Команда оставляет комментарии

# 6. Вносишь правки
git add .
git commit -m "Fix notification timing issue"
git push  # Автоматически обновит PR

# 7. После одобрения — мердж через интерфейс GitHub

Шаблон описания PR

markdown
## Описание
Добавлена система уведомлений для пользователей.

## Изменения
- ✅ Создана модель Notification
- ✅ Добавлен NotificationController
- ✅ Реализована отправка через email
- ✅ Добавлены тесты

## Тестирование
1. Зарегистрируйтесь как новый пользователь
2. Создайте пост
3. Проверьте, что пришло уведомление на email

## Скриншоты
[прикрепить изображение]

## Связанные issues
Closes #42
Related #38

## Чеклист
- [x] Код следует стандартам PSR-12
- [x] Добавлены тесты
- [x] Обновлена документация
- [x] Проверено локально

Код-ревью: на что смотреть

Как автор PR:

  • ✅ Маленький PR (до 400 строк)
  • ✅ Одна логическая задача
  • ✅ Описательные коммиты
  • ✅ Тесты проходят
  • ✅ Проверил сам перед отправкой

Как ревьюер:

  • 🔍 Понятность кода
  • 🔍 Соответствие требованиям
  • 🔍 Нет багов и уязвимостей
  • 🔍 Покрытие тестами
  • 🔍 Performance (нет N+1, медленных запросов)

🛠 Продвинутые команды

Stash: временное сохранение

bash
# Ситуация: работаешь, но нужно срочно переключиться
git stash  # Сохранить изменения во временное хранилище

# Переключаешься, делаешь срочную правку
git checkout main
git checkout -b hotfix/critical-bug
# ... исправляешь ...
git commit -m "Fix critical bug"

# Возвращаешься к своей работе
git checkout feature/my-work
git stash pop  # Восстановить изменения

# Посмотреть список stash
git stash list
# stash@{0}: WIP on feature/comments
# stash@{1}: WIP on feature/notifications

# Применить конкретный stash
git stash apply stash@{1}

# Удалить stash
git stash drop stash@{0}

Cherry-pick: взять конкретный коммит

bash
# Есть коммит в другой ветке, который нужен прямо сейчас
git log feature/other-branch --oneline
# abc123 Fix critical security issue
# def456 Add feature X

# Берёшь только нужный коммит
git cherry-pick abc123

# Теперь этот коммит есть в твоей текущей ветке

Reset: откат изменений

bash
# Мягкий откат (изменения остаются в файлах)
git reset --soft HEAD~1  # Отменить последний коммит

# Смешанный откат (по умолчанию, изменения остаются, но unstaged)
git reset HEAD~2  # Отменить 2 последних коммита

# Жёсткий откат (УДАЛЯЕТ изменения!)
git reset --hard HEAD~1  # ⚠️ Опасно! Вернуться нельзя

Reflog: история всех действий

bash
# Случайно удалил ветку? Сделал --hard reset?
git reflog
# abc123 HEAD@{0}: reset: moving to HEAD~1
# def456 HEAD@{1}: commit: Add feature
# ghi789 HEAD@{2}: checkout: moving from main to feature

# Восстановить удалённый коммит
git checkout def456
git checkout -b recovered-work

🎯 Практическое задание

Задание 1: Работа с ветками

Создай учебный репозиторий:

bash
mkdir git-practice
cd git-practice
git init

# Создай первый коммит
echo "# Git Practice" > README.md
git add README.md
git commit -m "Initial commit"

# Создай файл index.php
cat > index.php << 'EOF'
<?php
echo "Hello, World!";
EOF
git add index.php
git commit -m "Add index.php"

Задачи:

  1. Создай ветку feature/add-user-class
  2. Добавь файл User.php с простым классом
  3. Сделай коммит
  4. Вернись в main
  5. Создай ветку feature/add-product-class
  6. Добавь файл Product.php
  7. Смержи обе ветки в main
  8. Удали ветки фич

Задание 2: Разрешение конфликта

bash
# Создай конфликт специально
git checkout -b branch-a
echo "Version A" > conflict.txt
git add conflict.txt
git commit -m "Add version A"

git checkout main
git checkout -b branch-b
echo "Version B" > conflict.txt
git add conflict.txt
git commit -m "Add version B"

# Попробуй смержить
git checkout main
git merge branch-a  # ОК
git merge branch-b  # Конфликт!

# Разреши конфликт, оставив обе версии:
# Version A
# Version B

Задание 3: Rebase practice

bash
# main с двумя коммитами
git checkout main
echo "Line 1" > main.txt
git add main.txt
git commit -m "Main: add line 1"

echo "Line 2" >> main.txt
git commit -am "Main: add line 2"

# feature с двумя коммитами
git checkout -b feature/add-feature
echo "Feature line 1" > feature.txt
git add feature.txt
git commit -m "Feature: add line 1"

echo "Feature line 2" >> feature.txt
git commit -am "Feature: add line 2"

# Ребейзь feature на main
git rebase main

# Посмотри на чистую линейную историю
git log --oneline --graph --all

📋 Чек-лист: Git Best Practices

Коммиты

  • [ ] Каждый коммит = одно логическое изменение
  • [ ] Сообщение коммита понятное и описательное
  • [ ] Используешь настоящее время: "Add feature", не "Added feature"
  • [ ] Первая строка до 50 символов
  • [ ] Коммитишь часто (несколько раз в день)

Ветки

  • [ ] Используешь осмысленные имена с префиксами
  • [ ] Одна ветка = одна задача
  • [ ] Удаляешь смерженные ветки
  • [ ] Не работаешь напрямую в main
  • [ ] Синхронизируешь ветки с main регулярно

Merge vs Rebase

  • [ ] Используешь merge для публичных веток
  • [ ] Используешь rebase для локальной истории
  • [ ] Делаешь rebase перед созданием PR
  • [ ] Никогда не ребейзишь уже запушенные ветки других людей

Pull Requests

  • [ ] PR решает одну задачу
  • [ ] Описание PR понятное и полное
  • [ ] PR не больше 400 строк (в идеале)
  • [ ] Все тесты проходят
  • [ ] Сам проверил код перед отправкой

Конфликты

  • [ ] Понимаешь, почему возник конфликт
  • [ ] Тестируешь код после разрешения конфликта
  • [ ] Не используешь слепо "accept theirs/ours"

🐛 Частые ошибки

❌ Ошибка 1: Rebase запушенной ветки

bash
# Плохо:
git push origin feature/my-work
# ... через час ...
git rebase main
git push  # Ошибка! История изменилась

# Хорошо:
git push origin feature/my-work
# ... через час ...
git merge main  # Или делай rebase ДО первого push
git push

❌ Ошибка 2: Огромные коммиты

bash
# Плохо:
git add .
git commit -m "Update stuff"
# 47 files changed, 2,934 insertions(+), 876 deletions(-)

# Хорошо:
git add app/Models/User.php
git commit -m "Add avatar field to User model"

git add app/Http/Controllers/ProfileController.php
git commit -m "Add avatar upload to ProfileController"

git add resources/views/profile/edit.blade.php
git commit -m "Add avatar upload form to profile"

❌ Ошибка 3: Работа в main

bash
# Плохо:
git checkout main
# ... пишешь код ...
git commit -m "Add feature"
# Теперь main испорчен незавершённой фичей

# Хорошо:
git checkout -b feature/new-thing
# ... пишешь код ...
git commit -m "Add feature"
# main чистый, можно в любой момент сделать hotfix

❌ Ошибка 4: Игнорирование конфликтов

bash
# Плохо:
git merge feature/something
# CONFLICT in app/Models/User.php
git add app/Models/User.php  # Добавил, не проверив
git commit
# Приложение сломано, в коде остались <<<<<<< HEAD

# Хорошо:
git merge feature/something
# CONFLICT in app/Models/User.php
# Открываешь файл, внимательно разрешаешь конфликт
# Проверяешь синтаксис: php artisan serve
# Запускаешь тесты
git add app/Models/User.php
git commit

💡 Полезные алиасы

Добавь в ~/.gitconfig:

ini
[alias]
    # Короткие команды
    st = status
    co = checkout
    br = branch
    ci = commit
    
    # История
    lg = log --graph --oneline --all --decorate
    last = log -1 HEAD --stat
    
    # Разница
    df = diff
    dc = diff --cached
    
    # Откат
    undo = reset --soft HEAD~1
    unstage = reset HEAD --
    
    # Полезные
    aliases = config --get-regexp alias
    amend = commit --amend --no-edit

Использование:

bash
git st              # вместо git status
git co main         # вместо git checkout main
git lg              # красивая история
git undo            # отменить последний коммит
git amend           # добавить изменения к последнему коммиту

🎓 Итоги

Теперь ты знаешь:

✅ Как создавать и управлять ветками
✅ Разницу между merge и rebase
✅ Когда использовать merge, а когда rebase
✅ Как разрешать конфликты профессионально
✅ Как работать с Pull Requests
✅ Продвинутые команды Git
✅ Best practices командной разработки

Следующие шаги

В следующей главе изучим Docker для PHP — как упаковать приложение в контейнеры, настроить окружение разработки и забыть про "а у меня работает".


📚 Дополнительные ресурсы

Помни: Git — это не страшно. Даже если что-то сломается, почти всегда можно откатить изменения через git reflog. Экспериментируй, ошибайся, учись! 🚀

Выпущено под лицензией MIT