Git

rerere — сокращение от «reuse recorded resolution» (повторное использование сохраненных разрешений конфликтов). С помощью этой команды Git запоминает, каким образом был разрешен конфликт, чтобы при возникновении подобного, разрешить его автоматически.

Рассмотрим сценарии, в которых может пригодиться эта функциональность.

Сценарии

  1. Допустим, у вас есть ветка темы, которую вы объединяете с главной для поддержания актуального состояния. При этом необходимо избежать засорения истории множеством промежуточных коммитов. С помощью rerere можно периодически выполнять слияние, разрешать конфликты, а затем прерывать слияние. При завершении ветки темы можно выполнить финальное слияние, а rerere разрешит все проблемы автоматически.
  2. Допустим, вы объединяете множество изменений ветки темы с тестовой веткой. При возникновении ошибки в тестовой ветке можно отменить слияние, исправить ошибку и выполнить повторное слияние без повторного разрешения конфликтов.

Как это работает

Для запуска rerere введите следующую команду:

$ git config --global rerere.enabled true

Допустим, у нас есть файл с именем script.sh со следующим содержимым:

#!/bin/bash
# My first script

echo "Hello World!"

В главной ветке изменяем слово «Hello» на «Hola», а в ветке темы — «World» на «Git»:

При объединении двух ветвей возникает конфликт слияния:

$ git merge topic
Auto-merging script.sh
CONFLICT (content): Merge conflict in script.sh
Recorded preimage for 'script.sh'
Automatic merge failed; fix conflicts and then commit the result.

Это обычный конфликт слияния, но с дополнительным оператором «Recorded preimage for ‘script.sh'» (сохранение пред-образа для ‘script.sh’). При запуске git rerere status вместо git status можно выяснить, записан ли pre-merge-статус или пред-образ:

$ git rerere status
script.sh

Ниже показана информация, полученная с помощью git rerere diff и git diff:

Rerere сохраняет запись решения в папке .git/rr-cache. Заглянув в репозиторий, вы обнаружите файл preimage:

$ tree .git/rr-cache
.git/rr-cache
└── 54db390f1318184c7fb941c7c688546bdec9590a
    └── preimage

Запись исправления путем разрешения конфликта

Попробуем разрешить этот конфликт, объединив «Hola» в главной ветке и «Git» в ветке темы. В результате получаем «Hola Git». Запустите rerere diff еще раз, чтобы проверить сохраненную информацию:

$ git rerere diff
--- a/script.sh
+++ b/script.sh
@@ -1,8 +1,4 @@
 #!/bin/bash
 # My first script

-<<<<<<<
-echo "Hello Git!"
-=======
-echo "Hola World!"
->>>>>>>
+echo "Hola Git!"

Чтобы rerere запомнил разрешение конфликта, подготовьте файл и зафиксируйте изменение:

$ git add script.sh

$ git commit
Recorded resolution for 'script.sh'.
[master d0e6d1b] Merge branch 'topic'

rerere сохранил разрешение для ‘script.sh’ или пост-образ. Представим изменение с помощью диаграммы:

Это исправление теперь находится в postimage:

tree .git/rr-cache
.git/rr-cache
└── 54db390f1318184c7fb941c7c688546bdec9590a
    ├── postimage
    └── preimage
Содержимое предобраза и постобраза.

Таким образом, мы выполнили запись разрешения с помощью git rerere.

Для каждого файла, содержащего конфликт, git создает новую директорию с хешем, в которой будут находиться «пред-образ» и «пост-образ».

  • Когда rerere видит конфликт, он создает «пред-образ», содержимое которого такое же, как при запуске команды git diff.
  • Соответственно, «пост-образ» — это то, как выглядит файл после разрешения конфликта.

Как работает rerere:

Когда rerere видит конфликт для script.sh, он просматривает соответствующую директорию rr-cache, находит «пред-образ», который соответствует конфликту, и изменяет файл в рабочей директории для соответствия «пост-образу».

Воспроизведение конфликта для автоматического разрешения

Проверим, решится ли конфликт автоматически. Отменяем слияние, а затем перемещаем ветку темы поверх главной с помощью команды git reset -—hard HEAD^:

$ git reset --hard HEAD^
HEAD is now at ca1bf2b Change Hello to Hola

Затем извлекаем ветку темы и перемещаем ее:

$ git checkout topic
Switched to branch 'topic'

$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: Change World to Git
Using index info to reconstruct a base tree...
M script.sh
Falling back to patching base and 3-way merge...
Auto-merging script.sh
CONFLICT (content): Merge conflict in script.sh
Resolved 'script.sh' using previous resolution.
error: Failed to merge in the changes.
Patch failed at 0001 Change World to Git

Возникает тот же конфликт, однако появляется строка Resolved ‘script.sh’ using previous resolution (Разрешение ‘script.sh’ с помощью предыдущего разрешения). Заглянув в файл, мы обнаружим, что проблема уже решена:

$ cat script.sh
#!/bin/bash
# My first script

echo "Hola Git!"

git diff также показывает, как проблема была решена автоматически:

$ git diff
diff --cc script.sh
index 46e4937,3108049..0000000
--- a/script.sh
+++ b/script.sh
@@@ -1,4 -1,4 +1,4 @@@
  #!/bin/bash
  # My first script

- echo "Hola World!"
 -echo "Hello Git!"
++echo "Hola Git!"
Процесс перемещения

Добавив следующий код, мы завершаем процесс перемещения:

$ git add script.sh
$ git rebase --continue
Applying: Change World to Git

Еще одна настройка

Несмотря на то, что rerere автоматически разрешает конфликт, исправленные файлы остаются в состоянии unstaged. Это означает, что git add <file> необходимо выполнить вручную.

Чтобы rerere автоматически индексировал исправленные файлы, нужно воспользоваться следующей командой:

$ git config --global rerere.autoupdate true

Теперь rerere автоматически исправляет и индексирует файлы.

git rebase master
First, rewinding head to replay your work on top of it...
Applying: Change text to Hello Git
Using index info to reconstruct a base tree...
M script.sh
Falling back to patching base and 3-way merge...
Auto-merging script.sh
CONFLICT (content): Merge conflict in script.sh
Staged 'script.sh' using previous resolution.
error: Failed to merge in the changes.
Patch failed at 0001 Change World to Git

Продолжите перемещение для завершения:

$ git rebase --continue
Applying: Change World to Git

Заключение

Если вы выполняете множество повторных слияний, хотите поддерживать актуальность ветки темы с главной веткой или часто выполняете перемещение, то воспользуйтесь rerere. Просто включите его с помощью git config --global rerere.enabled true и предоставьте Git всю оставшуюся работу.

Читайте также:


Перевод статьи Minh Pham: The Git Rerere Command — Automate Solutions to Fix Merge Conflicts

Предыдущая статьяКак и для чего использовать нативную библиотеку сериализации Kotlin
Следующая статьяСможет ли Vue.js превзойти React в 2020 году?