Git
Chapters ▾ 2nd Edition

7.9 Git инструменти - Rerere

Rerere

Функционалността на git rerere е един вид скрита опция. Името идва от фразата “reuse recorded resolution” и както името подсказва, позволява ви се да укажете на Git да запомня как сте разрешили даден конфликт така че следващия път, когато той възникне отново — да бъде автоматично разрешен.

Има няколко сценария, когато това може да ви е от помощ. Един от примерите е упоменат в документацията и описва ситуация, в която искате да сте сигурни, че продължително съществуващ topic клон ще се слива чисто винаги, но не желаете да имате множество междинни сливащи къмити задръстващи историята ви. С разрешен rerere, можете да опитате случайно сливане, да разрешите конфликтите и след това да откажете сливането. Ако правите това продължително, тогава финалното сливане би трябвало да е лесно, защото rerere ще свърши корекциите вместо вас.

Същата тактика може да се използва, ако искате да пазите даден клон пребазиран и не желаете да се занимавате с едни и същи конфликти при пребазиране всеки път. Или, ако искате да вземете клон, който сте слели и в който сте разрешили много конфликти и след това пожелаете да го пребазирате — най-вероятно не искате да виждате всичките конфликти отново.

Друго приложение на rerere е когато случайно сливате множество развиващи се клонове в едно за тестване както Git проекта прави например. Ако тестовете не минават успешно, можете да превъртите назад сливанията и да ги повторите без участието на topic клона, който ги проваля без да трябва да решавате конфликтите отново.

За да активирате rerere функционалността, просто изпълнете:

$ git config --global rerere.enabled true

Можете да я разрешите и за конкретно хранилище създавайки директорията .git/rr-cache, но конфигурационната опция е по-чист начин и позволява глобална настройка.

Нека видим просто пример подобен на предишните. Имаме файл hello.rb със съдържание:

#! /usr/bin/env ruby

def hello
  puts 'hello world'
end

В един от клоновете ни сменяме думата “hello” на “hola”, след това в друг клон променяме “world” на “mundo”, точно както преди.

rerere1

Когато сливаме двата клона в едно, получаваме конфликт по съдържание:

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

Забелязваме новия ред от изхода, Recorded preimage for FILE. Освен него, всичко си изглежда като при нормален конфликт. На този етап rerere може да ни каже няколко неща. Нормално, можете да пуснете git status за да видите какъв е конфликта:

$ git status
# On branch master
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add <file>..." to mark resolution)
#
#	both modified:      hello.rb
#

Обаче, командата git rerere в допълнение ще ви уведоми, че е запомнила статуса преди сливането:

$ git rerere status
hello.rb

А git rerere diff ще ви покаже текущия статус на корекцията на конфликта — с какво сте започнали корекцията и как сте я завършили.

$ git rerere diff
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,11 @@
 #! /usr/bin/env ruby

 def hello
-<<<<<<<
-  puts 'hello mundo'
-=======
+<<<<<<< HEAD
   puts 'hola world'
->>>>>>>
+=======
+  puts 'hello mundo'
+>>>>>>> i18n-world
 end

Също така (и това няма връзка с rerere), можете да използвате git ls-files -u за да видите конфликтните файлове и техните версии — оригинална, лява и дясна:

$ git ls-files -u
100644 39804c942a9c1f2c03dc7c5ebcd7f3e3a6b97519 1	hello.rb
100644 a440db6e8d1fd76ad438a49025a9ad9ce746f581 2	hello.rb
100644 54336ba847c3758ab604876419607e9443848474 3	hello.rb

Сега можете да разрешите конфликта, така че редът да е puts 'hola mundo' и да пуснете git rerere diff отново, за да видите какво ще бъде запомнено от rerere:

$ git rerere diff
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,7 @@
 #! /usr/bin/env ruby

 def hello
-<<<<<<<
-  puts 'hello mundo'
-=======
-  puts 'hola world'
->>>>>>>
+  puts 'hola mundo'
 end

Казано с други думи това означава, че когато Git намери конфликт във файла hello.rb, при който има “hello mundo” от едната страна и “hola world” на другата, той ще го коригира автоматично използвайки “hola mundo”.

Сега можем да маркираме конфликта като разрешен и да къмитнем:

$ git add hello.rb
$ git commit
Recorded resolution for 'hello.rb'.
[master 68e16e5] Merge branch 'i18n'

Виждаме съобщението "Recorded resolution for FILE".

rerere2

Сега, нека да отменим това сливане и да го пребазираме на върха на master клона. Можем да преместим клона назад с git reset както видяхме в Мистерията на командата Reset.

$ git reset --hard HEAD^
HEAD is now at ad63f15 i18n the hello

Сега сливането ни е отменено. Следва да пребазираме topic клона.

$ git checkout i18n-world
Switched to branch 'i18n-world'

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

Сега получихме същия конфликт, който очакваме, но обърнете внимание на реда Resolved FILE using previous resolution в отпечатания изход. Ако погледнем файла ще видим, че той вече е коригиран и не съдържа маркери за конфликт.

#! /usr/bin/env ruby

def hello
  puts 'hola mundo'
end

Също, git diff ще ни покаже как е направена автоматичната корекция:

$ git diff
diff --cc hello.rb
index a440db6,54336ba..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
  #! /usr/bin/env ruby

  def hello
-   puts 'hola world'
 -  puts 'hello mundo'
++  puts 'hola mundo'
  end
rerere3

Можете също да пресъздадете конфликтния статус на файла с git checkout:

$ git checkout --conflict=merge hello.rb
$ cat hello.rb
#! /usr/bin/env ruby

def hello
<<<<<<< ours
  puts 'hola world'
=======
  puts 'hello mundo'
>>>>>>> theirs
end

Видяхме пример за това в Сливане за напреднали. Засега обаче, нека да го коригираме отново като просто изпълним git rerere повторно:

$ git rerere
Resolved 'hello.rb' using previous resolution.
$ cat hello.rb
#! /usr/bin/env ruby

def hello
  puts 'hola mundo'
end

Сега сме повторили корекцията автоматично използвайки кешираната от rerere информация за нея. Сега можете да добавите файла и да продължите пребазирането, за да го завършите.

$ git add hello.rb
$ git rebase --continue
Applying: i18n one word

И така, ако правите много повтарящи се сливания или пък искате да държите topic клон в синхрон с промените на master клона без много излишни сливания, или пък ако често пребазирате — можете да включите rerere, за да си улесните работата.