Команда git rebase — это очень мощная штука, и она позволяет делать воистину чудесные вещи. Но будьте осторожны, так как это деструктивная операция. Ей можно порушить историю очень сильно. Я уже как-то описывал эту команду, но сейчас я разберу её более подробно.
Подготовка данных
Создадим каталог для нашего проекта:
1 2 3 |
mkdir git-rebase-magic cd git-rebase-magic git init |
Теперь откройте ваш текстовый редактор и с помощью него создайте простой текстовый файл “text.txt” в каталоге “git-rebase-magic” со следующим содержимым:
1 2 3 4 5 6 7 8 9 10 |
one two three four five six seven eight nine ten |
Закоммитим этот файл:
1 2 |
git add text.txt git commit -m "First commit of text.txt" |
Создаём ветку “some-feature” и переключаемся на неё:
1 |
git checkout -b some-feature |
Внесите в текстовый файл следующие изменения и сохраните:
1 2 3 4 5 6 7 8 9 10 11 |
one two three added-witcher four five six seven eight nine ten |
Закоммитте их:
1 2 |
git add text.txt git commit -m "added-witcher" |
Снова откройте текстовый файл и сделайте его вот таким:
1 2 3 4 5 6 7 8 9 10 11 12 |
one two three added-witcher four five six seven added-kettle eight nine ten |
Сделайте коммит:
1 2 |
git add text.txt git commit -m "added-kettle" |
Откройте текстовый файл и поправьте его, чтобы он выглядел вот так:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
one two three added-witcher four five six seven added-kettle eight added-striga nine ten |
Коммит:
1 2 |
git add text.txt git commit -m "added-striga" |
Одна ветка готова. Подготовим вторую ветку:
1 2 |
git checkout master git checkout -b second-feature |
Поменяем содержимое нашего файла вот так:
1 2 3 4 5 6 7 8 9 10 11 |
one two three added-cauldron four five six seven eight nine ten |
Коммит:
1 2 |
git add text.txt git commit -m "added-cauldron" |
Снова поменяем содержимое нашего файла:
1 2 3 4 5 6 7 8 9 10 11 12 |
one two three added-cauldron four five six seven eight nine added-shrine ten |
Коммит:
1 2 |
git add text.txt git commit -m "added-shrine" |
Меняем файл в третий раз:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
one two three added-cauldron four five added-proof six seven eight nine added-shrine ten |
Коммит:
1 2 |
git add text.txt git commit -m "added-proof" |
В результате история веток репозитория будет выглядеть так:
Немного магии git rebase
Находясь в ветке “second-feature” перенесём наши изменения из ветки “some-feature” в ветку “second-feature”:
1 |
git rebase -i some-feature |
Откроется текстовый редактор со следующим содержимым:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
pick 3680d75 added-cauldron pick 99de9ad added-shrine pick 1553a8d added-proof # Перемещение 1fd69c5..1553a8d над 1fd69c5 (3 команды) # # Команды: # p, pick = использовать коммит # r, reword = использовать коммит, но изменить сообщение коммита # e, edit = использовать коммит, но остановиться для внесения правок # s, squash = использовать коммит, но объединить его с предыдущим коммитом # f, fixup = как «squash», но отбросить сообщение этого коммита # x, exec = выполнить команду (остаток строки) с помощью командной оболочки # d, drop = удалить коммит # # Эти строки могут быть перемещены; выполняются по очереди сверху вниз. # # Если вы удалите строку здесь, то УКАЗАННЫЙ КОММИТ БУДЕТ УТЕРЯН. # # Но если вы удалите все, то процесс перемещения будет будет прерван. # # Заметьте, что пустые коммиты закомментированны |
Допустим, что мы хотим перенести не все изменения, а только изменение, в котором мы добавили фразу “added-cauldron”. Тогда оставим pick у него, а остальным проставим drop:
1 2 3 |
pick 3680d75 added-cauldron drop 99de9ad added-shrine drop 1553a8d added-proof |
Обратите внимание, что в самом содержимом файла, который мы редактируем уже описаны все возможные команды. Сохраняем файл и выходим из редактора. Для nano это комбинация клавиш Ctrl+O, Enter, Ctrl+X.
Так как в ветке “some-feature” и “second-feature” были изменены одинаковые строки, то мы получим конфликт мержа:
1 2 3 4 5 6 |
error: не удалось применить коммит 3680d75… added-caulrdon Когда вы разрешите этот конфликт, запустите «git rebase --continue». Если вы хотите пропустить этот патч, то запустите «git rebase --skip». Чтобы перейти на оригинальную ветку и остановить перемещение, запустите «git rebase --abort». Не удалось применить 3680d75ab9d19bb948781720f575befaea5edeff… added-caulrdon |
Откройте текстовым редактором наш файл “text.txt” и исправьте конфликт. Исходный файл:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
one two three <<<<<<< HEAD added-witcher ======= added-cauldron >>>>>>> 3680d75... added-caulrdon four five six seven added-kettle eight added-striga nine ten |
Исправленный (мы приняли изменения из обоих веток, можно было оставить только одну):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
one two three added-witcher added-cauldron four five six seven added-kettle eight added-striga nine ten |
Коммитим изменения:
1 2 |
git add text.txt git commit -m "added-witcher and added-cauldron" |
Продолжаем rebase:
1 |
git rebase --continue |
Мы выбрали перенос (pick) только одного изменения, поэтому на этом наш rebase заканчивается:
1 |
Успешно перебазирован и обновлён refs/heads/second-feature. |
Если бы мы переносили несколько изменений, то выполнять команду git rebase --continue нужно было бы после каждой из них.
Результат:
В результате мы получили то, будто наша ветка “second-feature” отбранчевалась от коммита “added-striga” из ветки “some-feature”, и в ней был только один коммит “added-witcher and added-cauldron”. При желании мы могли бы перенести все коммиты.
Вывод: Команда git rebase -i some-feature сделала так, что наша текущая ветка будто бы изначально была сделана от текущей ветки “some-feature”, с последующим внесением тех изменений, который были в нашей ветке “second-feature”.
Если бы мы сейчас сделали pull request из ветки “second-feature” в ветку “some-feature”, то мы бы увидели только одно изменение “added-witcher and added-cauldron”.
При желании мы можем объединять коммиты. Например, сейчас мы находимся в ветке “second-feature”. Переключимся на ветку “some-feature” и смержим изменения туда:
1 2 |
git checkout some-feature git merge second-feature |
Результат будет аналогичный тому, что мы сделали pull request и смержили изменения из “second-feature” в ветку “some-feature”.
Добавим немного магии. Нам вовсе нет необходимости иметь аж четыре коммита. Объединим их всех в один:
1 |
git rebase -i HEAD~4 |
Цифра в “HEAD~4” означает какое количество коммитов в истории мы хотим поправить.
В открывшемся редакторе поправим текст вот так:
1 2 3 4 |
pick a6096d9 added-witcher squash abe6c6f added-kettle squash 1fd69c5 added-striga squash 7cd27df added-witcher and added-cauldron |
Сохраним файл. Нам предложат написать комментарий для объединённого коммита. Пусть это будет “squashed-commit”.
Теперь наш репозиторий будет выглядеть вот так:
Как видим наша ветка “some-feature” теперь содержит только один коммит “squashed-commit”, который содержит все изменения, что были в этой ветке до этого. Ветка “second-feature” осталась нетронутой и содержит все коммиты, что были в ней до этого.
Для того чтобы запушить ветки с изменённой с помощью git rebase истории, вам потребуется воспользоваться git push —force или git push —force-with-lease.
Теперь вы знаете git rebase!
Не нужен!
«В результате мы получили то, будто наша ветка “second-feature” отбранчевалась от коммита “added-striga” из ветки “some-feature”, и в ней был только один коммит “added-proof”.»
Автор, что ты несёшь? В результате никакого следа от коммита «added-proof» не останется, потому что ребейзили коммит «added-witcher and added-cauldron»
Возможно, я ошибся. Проверю на выходных.
Да, там ошибка. Всё поправил.