Russian (Pусский) translation by Sergey Zhuk (you can also view the original English article)
Основной рабочий процесс Git выглядит следующим образом: вы делаете задачу в отдельной ветке, затем по завершению сливаете ее в основную ветку. Это делает git merge
неотъемлемым инструментом для объединения веток. Однако это не единственный инструмент, который предлагает Git.



Альтернативой сценарию, представленному выше, может быть объединение веток с помощью команды git rebase
. Вместо слияние веток в специальном коммите, сдвиг перемещает всю ветку с задачей на конец master
ветки так, как показано ниже.



Это служит той же цели, что и git merge
, связывая коммиты из различных веток. Но есть две причины, по которым мы могли бы выбрать сдвиг вместо слияния:
- Сдвиг приводит к линейной истории проекта.
- Это дает нам возможность очистить историю от локальных коммитов.
В этой статье мы будет рассматривать эти два общих случая использования git rebase
. К сожалению преимущества git rebase
приводят нас к компромиссу. При неправильном использовании, может быть одной из наиболее опасных операций, которые можно выполнять в Git-репозиторий. Таким образом нужно внимательно рассмотреть опасности при сдвиге веток.
Необходимые условия
Эта статья предполагает, что вы знакомы с основами Git. Вы должны уметь сохранять и фиксировать изменения, разработку задач в отдельных ветках, соединять ветки, а так же сливать и заливать ветки из удаленных репозиториев.
1. Сдвиг для получения линейной истории
Первый вариант использования, который мы будем изучать, включает в себя расходящаяся история проекта. Рассмотрим репозиторий в котором основная ветка ушла вперед, пока вы работаете над задачей в другой ветке.



Чтобы сдвинуть ветку feature
на ветку master
, нужно выполнить следующие команды:
1 |
git checkout feature |
2 |
git rebase master |
Это перенесет ветку feature
из ее текущего положения на конец ветки master
.



Есть два возможных варианта, когда вам это может понадобится. Во-первых, ваша задача затрагивает новые коммиты из master
, и теперь они ей будут доступны. Во-вторых, если задача закрыта, то теперь ее можно быстро слить поверх ветки master
. В обоих случаях сдвиг приведет к линейной истории, тогда как git merge
приведет к необязательным коммитам слияния.
Например, рассмотрим, что произойдет, если вы будете использовать слияние вместо сдвига.
1 |
git checkout feature |
2 |
git merge master |
Это создаст дополнительный коммит слияния в ветке feature
. Более того это будет происходить каждый раз, когда вы хотели бы включить коммиты, находящиеся выше вашей ветки. В конце концов история вашего проекта будет завалена бессмысленными коммитами слияния.



Это же можно увидеть при слиянии и в другом направлении. Без использования сдвига, слияние ветки feature
в ветку master
так же требует коммита слияния. Хотя это и кажется значимым коммитом (он представляет отдельную законченную задачу), в итоге история будет полна ответвлений:



Если вы сделаете сдвиг перед слиянием, Git сможет наложить ветку master
на конец feature
. Увидеть линейную историю прогресса проекта можно с помощью команды git log
- коммиты из ветки feature
аккуратно сгруппированы поверх коммитов из master
. Это не обязательно тот случай, когда ветви сливаются вместе с коммитом слияния.



Разрешение конфликтов
При выполнении команды git rebase
, Git берет каждый коммит из ветки и переносит их по одному на новое место. Если любой из этих коммитов изменяет те же строчки, что и верхние по ветке коммиты, это приведет к конфликтам.



Команда git merge
позволяет вам разрешить все конфликты в конце слияния, что является одной из основных целей создания коммита слияния. Однако все это происходит немного по-другому при использовании сдвига. Конфликты разрешаются после каждого коммита. Таким образом, если git rebase
находит конфликт, то процедура сдвига приостанавливается и выводится сообщение с предупреждением:
1 |
Auto-merging readme.txt |
2 |
CONFLICT (content): Merge conflict in readme.txt |
3 |
Failed to merge in the changes.
|
4 |
.... |
5 |
When you have resolved this problem, run "git rebase --continue". |
6 |
If you prefer to skip this patch, run "git rebase --skip" instead.
|
7 |
To check out the original branch and stop rebasing, run "git rebase --abort". |
Визуально вот как выглядит история проекта, когда git rebase
обнаруживает конфликт:



Конфликты могут быть просмотрены с помощью git status
. Вывод выглядит очень похожим на конфликт при слиянии:
1 |
Unmerged paths: |
2 |
(use "git reset HEAD <file>..." to unstage) |
3 |
(use "git add <file>..." to mark resolution) |
4 |
|
5 |
both modified: readme.txt |
6 |
|
7 |
no changes added to commit (use "git add" and/or "git commit -a") |
Для разрешения конфликта следует открыть файл с конфликтами (в нашем примере это readme.txt), найти затрагиваемые строки, и вручную внести необходимые изменения. Затем сообщить Git, чтобы конфликт разрешен, зафиксировав изменения в файле:
1 |
git add readme.txt |
Обратите внимание, что это тот же самый способ, каким разрешаются конфликты при использовании git merge
. Но необходимо помнить, что мы все еще находимся в середине процесса сдвига, и не следует забывать об оставшихся коммитах. Последним шагом будет - сообщить Git, что можно продолжить сдвиг, используя опцию --continue
.
1 |
git rebase --continue
|
Оставшиеся коммиты по одному будут перемещены, если снова возникнут конфликты, то следует повторить процесс их разрешения.
Если вы не хотите разрешать конфликт, можно воспользоваться флагами --skip
или --abort
. Последний особенно полезен, если вы не знаете что происходит, и просто хотите безопасно вернуться назад.
1 |
# Ignore the commit that caused the conflict
|
2 |
git rebase --skip
|
3 |
|
4 |
# Abort the entire rebase and go back to the drawing board
|
5 |
git rebase --abort
|
2. Сдвиг для очищения от локальных коммитов
Пока мы только использовали git rebase
для перемещения веток, но это гораздо более мощный инструмент. Указав флаг -i
, можно начать интерактивную сессию сдвига. Интерактивное перемещение позволяет вам точно определить каким образом каждым коммит будет перемещен. Это дает возможность очистить историю задачи, перед тем как делиться ей с остальными разработчиками.
Например, допустим вы закончили работу в ветке feature
и готовы к сливанию ее в master
. Чтобы начать сессию интерактивного сдвига, необходимо выполнить следующую команду:
1 |
git checkout feature |
2 |
git rebase -i master
|
Будет открыт редактор со всеми коммитами из ветки feature
, которые нужно сдвинуть:
1 |
pick 5c43c2b [Description for oldest commit] |
2 |
pick b8f3240 [Description for 2nd oldest commit] |
3 |
pick c069f4a [Description for most recent commit] |
Этот список показывает, как ветка feature
будет выглядеть после перемещения. Каждая строчка представленная отдельный коммитом и командой pick
перед каждым хэшем коммита, определяет что будет происходить с коммитом во время сдвига. Обратите внимание, что коммиты перечислены от самого старого к новому. Изменяя этот список, вы получаете полный контроль над историей проекта.
Если нужно изменить порядок коммитов, то просто меняем строки местами. Если нужно изменить сообщение коммита, используем команду reword
. Если хотим соединить два коммита в один - меняем команду pick
на squash
. Это перенесет все изменения из данного коммита в тот, что находится выше. Например, если мы соединим второй коммит в списке с верхним, то ветка feature
после сохранениябудет выглядеть следующим образом:



Команда edit
является особенно мощной. При достижении определенного коммита Git останавливает процедуру сдвига, точно так же как при возникновении конфликта. Что дает возможность изменить содержимое коммита с помощью команды git commit --amend
или добавить новые коммиты с помощью стандартных команд git add
/git commit
. Любые новые коммиты станут частью новой ветки.
Интерактивный сдвиг может иметь глубокое воздействие на ваш процесс разработки. Можно сфокусироваться на написании кода, не заботясь о том, чтобы верно распределить свои изменения по коммитам. Если вы в конечном итоге решили соединить четыре разных коммита в один большой, то это не проблема - переписываем историю с помощью git rebase -i
и соединяем их в один.
3. Опасности сдвига
Теперь когда у вас есть представление о git rebase
, можно поговорить о том, когда его не следует применять. Внутри Git сдвиг в действительности же не перемещает коммиты на новую ветку. Вместо этого он создает новые, которые содержат нужные изменения. Имея это в виду, можно представить процесс сдвига следующим образом:



После сдвига коммиты в ветке feature
будут иметь другие хэши. Это означает, что мы не просто изменили положение ветки, а действительно переписали историю проекта. Это очень важный побочный эффект git rebase
.
Когда вы один работаете над проектом, переписывание истории не является очень существенным. Однако, как только вы начинаете работать в среде совместной работы, он может стать очень опасным. Если вы перепишите коммиты, которые используются другими разработчиками (например в ветке master
), то в следующий раз, когда они будут сливать себе ваши изменения, их собственные коммиты исчезнут. Что приведет к неприятных результатам, после которых тяжело будет все восстановить.
Имя это в виду, вам никогда не следует делать сдвиг коммитов, которые были залиты в публичный репозиторий, до тех пор пока вы не будете уверены, что никто их сейчас в своей работе не использует.
Заключение
В этой статье было представлено два наиболее частых способа использования команды git rebase
. Мы много обсуждали перемещение веток, но запомните главное, что сдвиг коммитов помогает контролировать историю вашего проекта. Возможность в будущем переписать коммиты, позволяет больше сосредоточиться на задачах разработки вместо разбиения работы на раздельные этапы.
Обратите внимание, что сдвиг является обязательным дополнением к вашему инструментарию Git. Вы все еще можете сделать все, что вам нужно с простой старой командой git commit
. Она гораздо безопаснее, поскольку позволяет избежать возможности переписывания общей истории. Однако если вы понимаете риски, с git rebase
можно получить гораздо чистую интеграцию веток, по сравнению со слиянием коммитов.