В общем случае команда git rebase
добавляет все коммиты из ветки, на которой вы находитесь, следующими за коммитом, который вы передаете git rebase
как аргумент. Если передать первым аргументом ветку, Git найдет последний общий коммит между текущей веткой и целевой веткой и добавит коммиты из текущей в целевую один за одним. Можно представить себе, что git rebase
по очереди "накатывает" каждый из коммитов вашей ветки на целевой коммит. При этом у всех "накатанных" коммитов изменяются метаданные (например, родительский коммит), поэтому с точки зрения Git это теперь новые коммиты.
На семинаре мы познакомились с двумя случаями применения git rebase
.
Для того, чтобы объединить две ветки в одну (например, когда работа над задачей закончена и нужно влить это задачу в ветку master
), часто используют команду git merge
. Это команда объединяет ветки путем создания отдельного коммита, у которого два родителя - коммиты-вершины каждой из веток на момент слияния.
Иногда команда git merge
вызывается неявно. Например, это может произойти при вызове команды git pull
. Если гит не сможет добавить новые изменения по стратегии fast-forward, создастся merge commit. Мерж-коммиты делают гит-историю нелинейной и плохо управляемой. Проблемы объединения веток при помощи git merge
:
- с историей тяжело работать из-за обилия мерж-коммитов (трудно разобраться)
- может быть трудно взять нужные коммиты в отдельную ветку при помощи cherry-pick, так как они будут зависеть от предыдущих
Примечание. Избежать нежелательного мерж-коммита при затягивании обновлений можно, если использовать git pull с флагом --ff-only
.
git rebase
избавляет от нежелательных мерж-коммитов, так как он не меняет целевую ветку, а коммиты из текущей ветки добавляет один за одним. Таким образом, ветки не смешиваются, мерж-коммита нет, а ваша работа оказывается в самом верху целевой ветки.
Дополнительно: Гит присваивает коммитам хеши и различает коммиты по хешам. Рекомендуем почитать о том, как работют хеш-функции (на семинаре эта тема не рассматривалась)
Упражнение
Исходное положение: коммит 0
Шаг 1. Создать ветки feat-1 и feat-2 от коммита 0. В каждую из веток добавить по 2-3 коммита, изменяющих один файл.
Объединить ветки при помощи git merge. Посмотреть, как выглядит история при помощи команды git logg
*.
Повторить Шаг 1
Объединить ветки при помощи git rebase
. Посмотреть, как выглядит история при помощи команды git logg
. Обратить внимание на изменение хешей коммитов при rebase
С одной стороны - желательно комитить почаще, чтобы не потерять работу. с другой стороны, в конце хочется иметь чистую и понятную историю, где каждый коммит представляет собой завершенный кусок работы, а не бесконечные "fix", "fixed fix" etc. Например, если вы работаете над несколькими файлами одновременно и коммитите изменения в каждый из них в случайном порядке, но хотим, чтобы в результате на изменение одного файла приходился только один коммит. Выход - сквош Для этого нужно познакомиться с другим применением git rebase: пересаживать коммиты можно не только на другую ветку, но и на предыдущий коммит в той же ветке. Какая от этого польза? Так как коммиты "накатываются" один за одним, в процессе мы можем их редактировать. Например, изменять commit message, менять коммиты местами или объединять несколько коммитов в один. Последний случай - это и есть squash.
Упражнение: Исходной положение: мы на коммите 0
- Внести любое изменение в файл А, сделать коммит 1
- Внести любое изменение в файл Б, сделать коммит 2
- Снова изменить файл А, сделать коммит 3
- При помощи
git rebase
исправить историю таким образом, чтобы было два коммита - один, меняющий файл А, и один, меняющий файл Б. Для этого нужно заребейсить три последних коммита на коммит 0 интерактивно, при этом поменяв местами коммиты 1 и 3 и "засквошив" их.
Важные замечания:
- Так как
git rebase
меняет коммиты, которые он добавляет в указанное место, нужно быть очень осторожным при rebase коммитов, которые уже отправлены на сервер. Ребейсить коммиты в общих ветках нельзя! - Если в процессе
git rebase
возникает конфликт,git rebase
останавливается, создает маркеры конфликта в файле (<<<<<<<<) и ждет, пока вы решите конфликт и явно продолжите процесс при помощиgit rebase --continue
. Отдельный коммит (как при мерже) для разрешения конфликта не создается (это еще один плюсgit rebase
)
Стратегии Git (например, ГитФлоу) позволяют дают готовое решение для разделения и изоляции работ таким образом, чтобы отдельные команды (или люди) работающие на проектом, не мешали друг другу. Например, тестировщики и разработчики работают со своей веткой и на своем сервере и не мешают друг другу. Как правило, разработчик создает для своей задачи отдельную ветку от ветки master, делает задачу и вливает свою ветку в master через пул-реквест. В зависимости от проекта и выбранной стратегии промежуточных этапов между завершением работы над задачей и попаданием кода в master может быть несколько - бывают отдельные ветки dev, qa, stage (названия примерные). Каждая такая ветка может быть залита (задеплоена) на отдельную машину, где часть команды (разработчики, тестировщики, продакт-оунер) может протестировать код.
*git logg
- это алиас для графического представление дерева коммитов: "log --graph --full-history --all --color --pretty=tformat:\"%x1b[31m%h%x09%x1b[32m%d%x1b[0m%x20%s\""
. Этот алиас будет доступен тем, кто настроил окружение согласно нашей инструкции.
@webuxmotion от коммита 0. По сути это значит "добавь ярлык ветки на коммит 0".