В продолжение статьи на тему что сказать git, чтобы он сделал то, что вам нужно и перед статьей как создать PR в чужой Open Source проект на GitHub думаю стоит полезным рассказать о том, что такое git rebase.
Итак git работает с комитами. Каждый комит — набор изменений. У каждого комита есть уникальный hash. Когда происходит слияние веток посредством merge:
то все комиты сохраняются — сохраняются комментарии комита, его hash + как правило добавляется еще один искусственный комит. При этом комиты могут чередоваться друг с другом. Это не всегда удобно. Допустим ваш комит решили откатить — выискивать в общем списке где ваш комит, а где не ваш не очень приятно. И вообще — в общей истории хочется видеть действительно важные изменения, а не «ой, я забыл поставить ;». Для того, чтобы несколько комитов склеивать в один можно использовать rebase. Хотя в интерфейсе GitHub есть кнопочка squash & commit — это когда вы создаете pull request (PR) из одной ветки в другую (как правило из вашей рабочей ветки в основную) и после прохождения всех формальностей можно нажать squash & commit, обновить комментарий и ваши изменения появятся в основной ветке как один комит.
Хочу написать о двух случаях использования rebase:
Итак вы все сделали в своей уютненькой веточки и решили поделиться этим комитом с миром, но мир хочет от вас только один комит. `git rebase -i ` запустит редактор и предложит отредактировать комиты (порядок следования комитов — сверху вниз в отличие от git log). Можно оставить комит как есть, можно изменить комментарий, можно склеить с предыдущим. Как правило ваш первый комит надо оставить как есть, а все остальные изменить на
При этом все комментарии, которые были в fixup комитах потеряются и будет использоваться комментарий от первого комита. Если вам были дороги комментарии, то стоит использовать squash вместо fixup.
Но если процесс разработки был долог, то скорее всего вам приходилось делать merge основной ветки. И все ваши комиты перемешаются с общими комитами и склеивать ваши с не вашими будет задачей непростой. Поэтому перед тем, как делать `git rebase -i <>` стоит сделать `git rebase`. `git rebase` поставит все ваши комиты в конец списка всех комитов (в чем можно убедиться запустив `git log`) и после этого запустиь `git rebase -i <HEAD~Количесво_ваших_комитов>`, во всех строчках кроме первой заменить pick → {fixup|squash} и вуаля — у вас один комит.
Если в процессе редактирования комитов `git rebase -i <>` вы как-то накосячили, то не стоит жать Control+C — exit code выхода из редактора git не волнует. Он просто возьмет файл и сделает все по нему. Просто удалите или закомментируйте все строчки в файле. git поймет, что вы ничего не хотели.
После манипуляций с rebase потребуется push с опцией -F. Все это потому, что мыпереписываем меняем историю комитов и git нас об этом честно предупреждает.
Сам я в этой теме не очень разбираюсь и не люблю ее использовать, по причинам изложенным ниже, поэтому прошу комментировать, дополню по возможности.
Итак git работает с комитами. Каждый комит — набор изменений. У каждого комита есть уникальный hash. Когда происходит слияние веток посредством merge:
# git merge "another_branch"
то все комиты сохраняются — сохраняются комментарии комита, его hash + как правило добавляется еще один искусственный комит. При этом комиты могут чередоваться друг с другом. Это не всегда удобно. Допустим ваш комит решили откатить — выискивать в общем списке где ваш комит, а где не ваш не очень приятно. И вообще — в общей истории хочется видеть действительно важные изменения, а не «ой, я забыл поставить ;». Для того, чтобы несколько комитов склеивать в один можно использовать rebase. Хотя в интерфейсе GitHub есть кнопочка squash & commit — это когда вы создаете pull request (PR) из одной ветки в другую (как правило из вашей рабочей ветки в основную) и после прохождения всех формальностей можно нажать squash & commit, обновить комментарий и ваши изменения появятся в основной ветке как один комит.
Другие варианты объединить несколько комитов в один
Я раньше делал так
Потом смотрел изменения в IDE, делал предкомитовые review, потом комитил и пушил на сервер.
Из плюсов — можно весь код, все изменения посмотреть в IDE. Иногда бывает удобнее, чем через web UI, который на GitHub.
# git checkout master && git pull && git branch -b <НоваяВетка> && git merge <СтараяВеткаГдеМногоКомитов> --squash
Потом смотрел изменения в IDE, делал предкомитовые review, потом комитил и пушил на сервер.
Из плюсов — можно весь код, все изменения посмотреть в IDE. Иногда бывает удобнее, чем через web UI, который на GitHub.
Осторожно rebase может менять hash комита и приводить к конфликтам слияний, особенно если над одной веткой трудятся несколько человек.
Хочу написать о двух случаях использования rebase:
- Когда изменения включаются из одну ветку в другую не посредством merge, а посредством rebase:
# git rebase "another_branch"
Это позволяет ваши локальные комиты поставить после всех комитов, которые были внесены в ветку «another_branch». Хэши ваших комитов изменятся.
- Когда можно руками отредактировать несколько ваших комитов — например склеить их, изменить коментарий:
# git rebase -i {HEAD~_commit_count_|commit_hash}
Примечание: стоит настроить редактор, который будет использоваться git-ом, перед вызовом этой команды. Лично я предпочитаю mcedit.
Итак вы все сделали в своей уютненькой веточки и решили поделиться этим комитом с миром, но мир хочет от вас только один комит. `git rebase -i ` запустит редактор и предложит отредактировать комиты (порядок следования комитов — сверху вниз в отличие от git log). Можно оставить комит как есть, можно изменить комментарий, можно склеить с предыдущим. Как правило ваш первый комит надо оставить как есть, а все остальные изменить на
pick "commit_hash" "comment" → fixup "commit_hash" "comment"
.При этом все комментарии, которые были в fixup комитах потеряются и будет использоваться комментарий от первого комита. Если вам были дороги комментарии, то стоит использовать squash вместо fixup.
Но если процесс разработки был долог, то скорее всего вам приходилось делать merge основной ветки. И все ваши комиты перемешаются с общими комитами и склеивать ваши с не вашими будет задачей непростой. Поэтому перед тем, как делать `git rebase -i <>` стоит сделать `git rebase`. `git rebase` поставит все ваши комиты в конец списка всех комитов (в чем можно убедиться запустив `git log`) и после этого запустиь `git rebase -i <HEAD~Количесво_ваших_комитов>`, во всех строчках кроме первой заменить pick → {fixup|squash} и вуаля — у вас один комит.
Если в процессе редактирования комитов `git rebase -i <>` вы как-то накосячили, то не стоит жать Control+C — exit code выхода из редактора git не волнует. Он просто возьмет файл и сделает все по нему. Просто удалите или закомментируйте все строчки в файле. git поймет, что вы ничего не хотели.
После манипуляций с rebase потребуется push с опцией -F. Все это потому, что мы
# git push -f
Пример использования git rebase
Вдогонку напишу что у меня получилось. Дано — 2 ветки — master и b1. В процессе работы мы сделали комиты:
Комиты в master и b1 были сделаны независимо. Написал в один список, чтобы был понятен порядок в котором все делалось. Вот список комитов в каждом бранче:
Делаем git merge master в b1
Добавился новый синтетический комит
Теперь делаем rebase
Обратите внимание — наши локальные комиты встали в конец списка, номера комитов изменились, синтетический комит исчез.
Ну и напоследок склеиваем наши комиты в один
Файл было
Файл стало после нашего редактирования
Получилось
Такие дела
Initial commit - ветка master - 2fbbe67
b1.1 - ветка b1 - 85eac43
master after b1.1 - ветка мастер b505f18
b1.2 - ветка b1 - 2d7d4ea
+1 - ветка master - 8dcef6c
Комиты в master и b1 были сделаны независимо. Написал в один список, чтобы был понятен порядок в котором все делалось. Вот список комитов в каждом бранче:
# git checkout master && git log
8dcef6c "+1"
b505f18 "master after b1.1"
2fbbe67 "Initial commit"
# git checkout b1 && git log
2d7d4ea "b1.2"
85eac43 "b1.1"
2fbbe67 "Initial commit"
Делаем git merge master в b1
# git checkout b1 && git merge master && git log
5383781 "Merge branch 'master' into b1"
8dcef6c "+1"
2d7d4ea "b1.2"
b505f18 "master after b1.1"
85eac43 "b1.1"
2fbbe67 "Initial commit"
Добавился новый синтетический комит
Теперь делаем rebase
# git checkout b1 && git rebase master && git log
7f18e47 "b1.2"
6fb80cb "b1.1"
8dcef6c "+1"
b505f18 "master after b1.1"
2fbbe67 "Initial commit"
Обратите внимание — наши локальные комиты встали в конец списка, номера комитов изменились, синтетический комит исчез.
Ну и напоследок склеиваем наши комиты в один
# git rebase -i HEAD~2
Файл было
pick 6fb80cb b1.1
pick 7f18e47 b1.2
Файл стало после нашего редактирования
pick 6fb80cb b1.1
fixup 7f18e47 b1.2
Получилось
# git checkout b1 && git log
9062cd7 "b1.1"
8dcef6c "+1"
b505f18 "master after b1.1"
2fbbe67 "Initial commit"
Такие дела