Глава 14.1: Git глубже — branching, merge, rebase, pull requests, конфликты
🎯 Чему научимся
После этой главы ты сможешь:
- Создавать и управлять ветками для разных фич и исправлений
- Понимать разницу между merge и rebase и знать, когда что использовать
- Разрешать конфликты слияния профессионально
- Работать с pull requests в командной разработке
- Использовать продвинутые Git-команды для управления историей
📖 Зачем нужны ветки?
Проблема без веток
Представь: ты работаешь над интернет-магазином. Тебе нужно:
- Добавить новую систему оплаты (неделя работы)
- Срочно исправить баг в корзине (1 час)
- Экспериментировать с новым дизайном
Если работать в одной ветке:
- Недописанная оплата сломает проект
- Нельзя выпустить исправление бага, не выпустив недоделанную оплату
- Эксперименты могут сломать рабочий код
Решение: ветки
main (рабочая версия)
├── feature/payment-gateway (новая фича)
├── bugfix/cart-total (срочное исправление)
└── experiment/redesign (эксперимент)Каждая задача живёт в своей ветке, не мешая другим.
🌿 Работа с ветками
Создание и переключение
# Посмотреть все ветки
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 # создать и переключитьсяПрактический пример: работа над фичей
# Ты на 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
# ...Именование веток: соглашения
# ✅ Хорошие имена
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/— срочное исправление для productionrefactor/— рефакторинг без новых фичexperiment/— эксперименты, может быть удаленаrelease/— подготовка релиза
🔀 Merge: слияние веток
Как работает merge
main: A---B---C
\
feature: D---E---F
После git merge feature:
main: A---B---C-------M
\ /
feature: D---E---FMerge создаёт 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 не создаётся.
Практика: слияние фичи
# Закончили работу над комментариями
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
# Всегда создавать 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?
Чистая линейная история:
# С 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 перед слиянием
# Ты работаешь в 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: редактирование истории
# Посмотреть последние 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) ✅ Хочешь сохранить полную историю изменений ✅ Работаешь в команде и ветка уже запушена в удалённый репозиторий ✅ Важно видеть, когда фичи были влиты
git checkout main
git merge feature/add-commentsRebase — используй когда:
✅ Работаешь в локальной ветке, которую ещё не пушил ✅ Хочешь чистую линейную историю ✅ Синхронизируешь свою ветку с актуальным main ✅ Чистишь историю перед pull request
git checkout feature/add-comments
git rebase main⚠️ Золотое правило rebase
НИКОГДА не делай rebase публичных веток!
# ❌ ОПАСНО! Не делай так:
git checkout main
git rebase feature/something # Переписываешь main!
# Если main уже запушен, это сломает работу всей командыПочему? Rebase переписывает историю. Если кто-то уже скачал старую версию main, у него будут конфликты.
Практический workflow
# 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
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— версия из ветки
Разрешение конфликта
# 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
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);
}
}# 2. Отмечаешь конфликт как разрешённый
git add app/Models/User.php
# 3. Продолжаешь merge
git commit # Для merge
# или
git rebase --continue # Для rebase
# Если хочешь отменить весь процесс:
git merge --abort
# или
git rebase --abortИнструменты для разрешения конфликтов
# Запустить mergetool (визуальный редактор)
git mergetool
# Использовать VS Code (рекомендуется)
code .
# VS Code показывает кнопки:
# - Accept Current Change (твоя версия)
# - Accept Incoming Change (их версия)
# - Accept Both Changes (обе версии)
# - Compare Changes (сравнить)Стратегии при конфликтах
# При 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
# 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
## Описание
Добавлена система уведомлений для пользователей.
## Изменения
- ✅ Создана модель 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: временное сохранение
# Ситуация: работаешь, но нужно срочно переключиться
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: взять конкретный коммит
# Есть коммит в другой ветке, который нужен прямо сейчас
git log feature/other-branch --oneline
# abc123 Fix critical security issue
# def456 Add feature X
# Берёшь только нужный коммит
git cherry-pick abc123
# Теперь этот коммит есть в твоей текущей веткеReset: откат изменений
# Мягкий откат (изменения остаются в файлах)
git reset --soft HEAD~1 # Отменить последний коммит
# Смешанный откат (по умолчанию, изменения остаются, но unstaged)
git reset HEAD~2 # Отменить 2 последних коммита
# Жёсткий откат (УДАЛЯЕТ изменения!)
git reset --hard HEAD~1 # ⚠️ Опасно! Вернуться нельзяReflog: история всех действий
# Случайно удалил ветку? Сделал --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: Работа с ветками
Создай учебный репозиторий:
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"Задачи:
- Создай ветку
feature/add-user-class - Добавь файл
User.phpс простым классом - Сделай коммит
- Вернись в main
- Создай ветку
feature/add-product-class - Добавь файл
Product.php - Смержи обе ветки в main
- Удали ветки фич
Задание 2: Разрешение конфликта
# Создай конфликт специально
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
# 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 запушенной ветки
# Плохо:
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: Огромные коммиты
# Плохо:
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
# Плохо:
git checkout main
# ... пишешь код ...
git commit -m "Add feature"
# Теперь main испорчен незавершённой фичей
# Хорошо:
git checkout -b feature/new-thing
# ... пишешь код ...
git commit -m "Add feature"
# main чистый, можно в любой момент сделать hotfix❌ Ошибка 4: Игнорирование конфликтов
# Плохо:
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:
[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Использование:
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 — как упаковать приложение в контейнеры, настроить окружение разработки и забыть про "а у меня работает".
📚 Дополнительные ресурсы
- Pro Git Book — официальная книга
- Oh My Git! — игра для изучения Git
- Learn Git Branching — интерактивное обучение
- Git Flight Rules — решения частых проблем
Помни: Git — это не страшно. Даже если что-то сломается, почти всегда можно откатить изменения через git reflog. Экспериментируй, ошибайся, учись! 🚀