Два способа интеграции изменений
Когда вы работаете с branch в Git, наступает момент, когда нужно перенести изменения из одной branch в другую. Git предлагает для этого две основные стратегии: merge и rebase. Обе достигают одного и того же конечного результата -- ваша branch содержит все последние изменения -- но делают это принципиально разными способами, и выбор правильного важен для истории вашего проекта и workflow вашей команды.
Как работает Merge
Merge берёт две branch и объединяет их, создавая новый merge commit. Этот merge commit имеет двух родителей: вершину вашей текущей branch и вершину branch, которую вы интегрируете.
git checkout feature/login
git merge main
После этого ваша фича-branch содержит все изменения из main, соединённые merge commit. История обеих branch сохраняется именно так, как она происходила. Если посмотреть на граф commit, вы увидите две линии разработки, сходящиеся в точке merge.
Ключевые характеристики merge:
- Неразрушающий. Никакие существующие commit не изменяются. История — это именно то, что произошло.
- Создаёт merge commit. Этот дополнительный commit связывает две истории воедино.
- Сохраняет контекст. Вы всегда можете увидеть, когда branch была создана и когда была объединена обратно.
Fast-forward merge
Если ваша branch не разошлась с целевой (то есть на целевой нет новых commit с момента создания вашей branch), Git по умолчанию выполняет fast-forward merge. Он просто перемещает указатель branch вперёд. Merge commit не создаётся, поскольку в нём нет необходимости. Вы можете принудительно создать merge commit даже в этом случае:
git merge --no-ff feature/login
Это полезно, когда вы хотите сохранить тот факт, что работа велась в отдельной branch, даже если merge был тривиальным.
Как работает Rebase
Rebase использует другой подход. Вместо создания merge commit он воспроизводит ваши commit поверх другой branch. Результат — линейная история, которая выглядит так, будто вы начали работу с последней точки целевой branch.
git checkout feature/login
git rebase main
Что происходит за кулисами:
- Git находит общего предка двух branch.
- Временно убирает commit вашей branch.
- Перемещает указатель вашей branch на вершину main.
- Воспроизводит ваши commit один за другим поверх.
Получившаяся история идеально линейна. Нет merge commit, нет расходящихся и сходящихся линий на графе. Выглядит так, будто вы написали весь код после последних изменений на main, даже если на самом деле это не так.
Ключевые характеристики rebase:
- Создаёт линейную историю. Граф commit чистый и легко читаемый.
- Переписывает хеши commit. Воспроизведённые commit получают новые SHA-хеши, хотя их содержимое остаётся прежним.
- Без merge commit. Branch интегрируется чисто, без дополнительных commit.
Когда использовать Merge
Merge — более безопасный и простой выбор в нескольких ситуациях:
- Общие branch. Когда несколько человек работают на одной branch, merge сохраняет историю каждого без переписывания commit, от которых зависят другие.
- Важно сохранить историю. В проектах, где нужно точно отслеживать, когда и как были интегрированы изменения, merge предоставляет полную, неизменённую запись.
- Интеграция долгоживущих branch. При merge release-branch обратно в main или объединении двух branch, которые значительно разошлись, merge commit чётко отмечает точку интеграции.
- Вы хотите простоту. Merge концептуально проще. Меньше вещей может пойти не так, особенно для разработчиков, начинающих работать с Git.
Когда использовать Rebase
Rebase блистает, когда вы хотите чистую, линейную историю проекта:
- Обновление фича-branch. Перед merge вашей функциональности в main, rebase на последний main даёт вам чистый набор commit, которые применяются напрямую без merge commit.
- Поддержание чистой истории. Линейная история проще для навигации с
git log, проще для bisect при поиске багов и проще для ревью в pull request. - Локальная уборка. Если вы делали маленькие инкрементальные commit во время работы, вы можете использовать rebase для наведения порядка перед тем, как поделиться branch.
Золотое правило Rebase
Никогда не делайте rebase commit, которые были push на общую branch.
Это самое важное правило, которое нужно запомнить. Поскольку rebase переписывает хеши commit, у всех, кто строил свою работу на оригинальных commit, возникнут проблемы. Их история больше не совпадает с rebase-историей, что приводит к дублированию commit, конфликтам и путанице.
Правило простое: если другие люди могли сделать pull ваших commit, не делайте rebase. Используйте rebase только на своих локальных branch или на branch, где вы единственный контрибьютор.
Если вы случайно сделали rebase общей branch, другим разработчикам команды придётся разбираться с расходящейся историей. Обычно это означает force-push (который перезаписывает remote) и необходимость для всех остальных сделать reset своих локальных копий. Это разрушительно и предотвратимо.
Интерактивный Rebase: точное переписывание истории
Интерактивный rebase (git rebase -i) выходит за рамки простого перемещения commit. Он позволяет изменять саму историю commit:
git rebase -i HEAD~5
Это открывает список последних 5 commit, где вы можете:
- pick -- оставить commit как есть
- reword -- изменить сообщение commit
- edit -- приостановиться для изменения commit
- squash -- объединить с предыдущим commit
- fixup -- как squash, но отбросить сообщение commit
- drop -- полностью удалить commit
- reorder -- изменить порядок, переставив строки
Интерактивный rebase мощен для приведения в порядок беспорядочной branch перед merge. Вы можете сделать squash commit «исправление опечатки» в их родителя, переформулировать неясные сообщения и переупорядочить commit так, чтобы они рассказывали логичную историю.
Практический рабочий процесс
Многие команды используют комбинированный подход, получая лучшее от обеих стратегий:
- Создать фича-branch от main.
- Во время работы периодически делать rebase фича-branch на main для поддержания актуальности.
- Перед открытием pull request использовать интерактивный rebase для приведения commit в порядок.
- Сделать merge фича-branch в main (часто с merge commit для обозначения точки интеграции).
Это даёт вам чистую, линейную историю на фича-branch и чёткую точку merge на main. Это прагматичный компромисс, который хорошо работает для большинства команд.
Rebase и Merge в GitSquid
GitSquid поддерживает как merge, так и rebase прямо из интерфейса. Вы можете делать merge branch через контекстное меню на любом ярлыке branch в графе commit. Для rebase GitSquid включает визуальный редактор интерактивного rebase, который позволяет переупорядочивать, squash, fixup, reword и drop commit перетаскиванием и кликами, а не редактированием текстового файла в терминале. Это делает интерактивный rebase доступным, даже если версия командной строки кажется вам пугающей.
Итоги
| Merge | Rebase | |
|---|---|---|
| История | Нелинейная, сохраняет branch | Линейная, чистая |
| Хеши commit | Без изменений | Переписаны |
| Merge commit | Да (кроме fast-forward) | Нет |
| Безопасно для общих branch | Да | Нет |
| Лучше всего для | Интеграции совместной работы | Приведения в порядок фича-branch |
Ни merge, ни rebase не является универсально лучшим. Это инструменты для разных ситуаций. Поймите, что каждый из них делает с вашей историей, следуйте золотому правилу rebase и выбирайте подходящий для контекста. Ваше будущее «я», читающее git log, скажет вам спасибо.