Материал подготовлен командой Simple-Server для администраторов VPS и выделенных серверов. Команды и пути проверяйте на тестовой машине перед production.
Кратко о задаче
Понять устройство ребазирования в Git лучше всего на примере абстрактного репозитория, состоящего из нескольких веток. При этом операцию ребазирования необходимо рассматривать поэтапно.**
**
Предположим мы создали репозиторий с единственной веткой master, в которую сделали только один коммит. Ветка master приобрела следующий вид:
После этого на основе master мы создали новую ветку hypothesis, внутри который решили протестировать некоторые фичи. Внутри новой ветки мы сделали несколько коммитов, улучшающих кодовую. Ветка приобрела такой вид:
hypothesis
* `commit_4`
* `commit_3`
* `commit_2`
* `commit_1`А уже потом мы добавили еще один коммит в ветку master, исправив некоторую уязвимость в срочном порядке. Таким образом, ветка master стала выглядеть так:
Теперь наш репозиторий имеет структуру из двух веток:
hypothesis
* `commit_4`
* `commit_3`
* `commit_2`
* `commit_1`Ветка master является основной, а hypothesis — второстепенной (производной). Поздние коммиты указываются выше ранних, подобно тому, как их выводит команда git log.
Предположим, мы хотим продолжить работу над улучшением фичи, под которую ранее выделили отдельную ветку hypothesis. Однако эта ветка не содержит жизненно важное исправление уязвимости, которое мы сделали в ветке master.
Поэтому нам хотелось бы «синхронизировать» состояние ветки hypothesis с веткой master так, чтобы коммит исправления оказался в ветке с фичей. То есть мы хотим получить примерно такую структуру репозитория:
hypothesis
* `commit_4`
* `commit_3`
* `commit_2`
* `commit_5`
* `commit_1`Как видно, ветка hypothesis будет точно повторять историю изменений ветки master, несмотря на то, что изначально она была создана до коммита commit_5. Иными словами, ветка hypothesis будет содержать историю обеих веток — и свою, и master.
Чтобы получить такой результат, необходимо выполнить ребазирование с помощью команды git rebase.
Впоследствии изменения, сделанные в hypothesis, можно будет объединить с веткой master с помощью классической команды git merge, создающей коммит слияния.
Тогда структура репозитория станет такой:
master
* `commit_merge`
* `commit_5`
* `commit_1`* `commit_4`
* `commit_3`
* `commit_2`
* `commit_5`
* `commit_1`Более того, выполнение git merge после команды git rebase может снизить вероятность возникновения конфликтов.
Разобравшись с теоретическим аспектом команды git rebase, можно перейти к ее тестированию в реальном репозитории некого импровизированного проекта. При этом структура репозитория будет повторять показанный ранее теоретический пример.**
**
Для начала создадим отдельную директорию, в которой будет размещаться репозиторий:
После чего перейдем в нее:
Теперь можно инициализировать репозиторий:
В консольном терминале должно вывестись стандартное информационное сообщение:
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
...А в текущей директории появится скрытый каталог .git, который можно увидеть с помощью соответствующей команды:
Флаг -a означает all и позволяет просматривать файловую систему в расширенном режиме. Ее содержимое будет таким:
Перед тем, как начать делать коммиты, необходимо указать базовые сведения о пользователе.
git config --global user.name "ИМЯ"
git config --global user.email "ИМЯ@ХОСТ.COM"С помощью простых текстовых файлов мы будем имитировать добавление различных функций в проект. Каждая новая функция будет оформляться в отдельный коммит.
Создадим файл импровизированной функции:
И наполним его содержимом:
Теперь проиндексируем изменения, сделанные в репозитории:
На всякий случай можно проверить статус индексации:
В консольном терминале должно появится сообщение со списком проиндексированных изменений:
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: function_1Теперь можно выполнить коммит:
После этого в консольном терминале выведется сообщение об успешном внесении изменений в ветку master:
[master (root-commit) 4eb7cc3] commit_1
1 file changed, 1 insertion(+)
create mode 100644 function_1Наполнение ветки hypothesis
Теперь необходимо создать новую ветку hypothesis:
git checkout -b hypothesisФлаг -b необходим, чтобы сразу переключиться на созданную ветку.
В консольном терминале появится сообщение об успешном переключении на новую ветку:
Switched to a new branch 'hypothesis'Теперь необходимо последовательно выполнить три коммита с тремя файлами по аналогии с веткой master:
-
commit_2с файломfunction_2и содержимымФункция 2 -
commit_3с файломfunction_3и содержимымФункция 3 -
commit_4с файломfunction_4и содержимымФункция 4
commit_2 с файлом function_2 и содержимым Функция 2
commit_3 с файлом function_3 и содержимым Функция 3
commit_4 с файлом function_4 и содержимым Функция 4
Если после этого просмотреть список всех коммитов:
То в консольном терминале появится такая последовательность:
d3efb82 (HEAD -> hypothesis) commit_4
c9f57b7 commit_3
c977f16 commit_2
4eb7cc3 (master) commit_1В этой команде флаг --oneline необходим для вывода информации о коммитах в сжатом формате одной строки.
Последнее, что нужно сделать, — добавить еще один коммит в основную ветку master. Переключимся на нее:
В консольном терминале должно появится соответствующее сообщение:
Switched to branch 'master'После этого создадим еще один файл импровизированной функции:
Содержимое будет следующим:
Теперь можно проиндексировать изменения:
И выполнить очередной коммит:
Если проверить текущий список коммитов:
То в ветке master их будет всего два:
3df7a00 (HEAD -> master) commit_5
4eb7cc3 commit_1Объединение веток с git rebase
Для выполнения перебазирования сперва необходимо перейти в ветку hypothesis:
И выполнить ребазирование:
После этого в консольном терминале появится сообщение об успешном ребазировании:
Successfully rebased and updated refs/heads/hypothesis.Теперь можно проверить список коммитов:
В консольном терминале появится список, содержащий коммиты обоих веток в оригинальном порядке:
8ecfd58 (HEAD -> hypothesis) commit_4
f715aba commit_3
ee47470 commit_2
3df7a00 (master) commit_5
4eb7cc3 commit_1Теперь ветка hypothesis содержит общую историю всего репозитория.**
**
Как и в случае с git merge, при использовании команды git rebase могут возникать конфликты, требующие ручного разрешения.
Давайте модифицируем наш репозиторий таким образом, чтобы искусственно создать конфликт ребазирования.
Создадим в ветке hypothesis еще один файл:
И запишем в него следующий текст:
Тут должен быть конфликт!Проиндексируем изменения:
И выполним очередной коммит:
git commit -m "conflict_1"Теперь переключимся на ветку master:
Создадим аналогичный файл:
И наполним его следующим содержимым:
Тут НЕ должен быть конфликт!Аналогично, выполняем индексацию:
git commit -m "conflict_2"Заново откроем созданный файл:
И изменим его содержимое на следующий текст:
Тут точно НЕ должен быть конфликт!
git commit -m "conflict_3"Теперь можно обратно переключиться на ветку hypothesis:
А далее выполнить еще одно ребазирование:
В консольном терминале появится сообщение о конфликте:
Auto-merging conflict
CONFLICT (add/add): Merge conflict in conflict
error: could not apply 6003ed7... conflict_1
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 6003ed7... conflict_1Git предлагает отредактировать файл conflict, проиндексировать изменения с помощью команды git add, после чего продолжить ребазирование, указав флаг --continue.
Именно так мы и поступим:
Файл будет содержать две конфликтующие версии файла, обрамленные специальными символами:
<<<<<<< HEAD
Тут точно НЕ должен быть конфликт!
=======
Тут должен быть конфликт!
>>>>>>> 6003ed7 (conflict_1)Наша задача убрать всё лишнее, наполнив файл итоговым вариантом произвольного текста:
Тут однозначно точно единогласно НЕ должен быть никакого конфликта!Теперь индексируем изменения:
И продолжаем процесс ребазирования:
После этого в консольном терминале откроется текстовый редактор, предлагающий изменение оригинальное название того коммита, в котором возник конфликт:
conflict_1
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# interactive rebase in progress; onto bd7aefc
# Last commands done (4 commands done):
# pick 8ecfd58 commit_4
# pick 6003ed7 conflict_1
# No commands remaining.
# You are currently rebasing branch 'hypothesis' on 'bd7aefc'.
#
# Changes to be committed:
# modified: conflict
#В консольном терминале появится сообщение об успешном завершении процесса ребазирования:
[detached HEAD 482db49] conflict_1
1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/hypothesis.Теперь если проверить список коммитов в ветке hypothesis:
Можно увидеть оригинальную последовательность всех сделанных изменений:
482db49 (HEAD -> hypothesis) conflict_1
bd5d036 commit_4
407e245 commit_3
948b41c commit_2
bd7aefc (master) conflict_3
d98648d conflict_2
3df7a00 commit_5
4eb7cc3 commit_1Обратите внимание, что коммиты conflict_2 и conflict_3, сделанные в ветке master, располагаются в истории изменений раньше, чем коммит conflict_1. Впрочем, это касается любых коммитов, сделанных в ветке master.**
**
Ребазирование удаленного репозитория
Помимо работы с локальными ветками, ребазирование можно выполнить в момент подтягивания изменений из удаленного репозитория. Для этого к стандартной команде pull необходимо добавить флаг --rebase:
git pull --rebase remote branch-
remote. Удаленный репозиторий. -
branch. Удаленная ветка.
remote. Удаленный репозиторий.
branch. Удаленная ветка.
По сути, такая конфигурация команды pull является эквивалентом git rebase за исключением того, что применяемые к текущей ветки изменения (коммиты) берутся из удаленного репозитория.
Команда git rebase позволяет сформировать достаточно линейную историю целевой ветки, представляющую собой последовательно сделанные коммиты.
Такая последовательность и отсутствие ветвления делает историю проще для восприятия и понимания.**
**
- Снижение количества конфликтов
Предварительно выполненная команда git rebase может существенно снизить вероятность возникновения конфликтов при объединении веток с помощью git merge.
Конфликты проще разрешать в последовательно идущих коммитах, нежели в коммитах, сливающихся в один единый коммит слияния. Это особенно актуально при отправке веток в удаленные репозитории.**
**
В отличие от слияния, ребазирование частично переписывает историю целевой ветки. При этом лишние элементы истории удаляются.**
**
Свойство существенно перестраивать историю коммитов может приводить к необратимым ошибкам внутри репозитория. А это значит, что некоторые данные могут быть безвозвратно утеряны.**
**
Нужен сервер для практики? Арендуйте VPS/VDS в России — root-доступ, NVMe, DDoS-защита и поддержка 24/7.