Git
Chapters ▾ 2nd Edition

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

Rerere

Можливості git rerere є трохи прихованими. Назва походить від “використовуй записані розвʼязання” (reuse recorded resolution) та, як зрозуміло з назви, дозволяє вам попросити Git запамʼятати, як ви розвʼязали конфлікт шматків (hunk), щоб наступного разу Git міг автоматично розвʼязати такий самий конфлікт для вас.

Є декілька ситуацій, в яких цей функціонал може бути дуже доречним. Одна з таких ситуацій згадується в документації: коли ви бажаєте переконатись, що довготривала тематична гілка зіллється переважно чисто, проте не бажаєте, щоб купа проміжних комітів злиття засмічували історію. Якщо rerere увімкнено, ви можете іноді зливати, розвʼязувати конфлікти, а потім припиняти злиття. Якщо робити це постійно, то останнє злиття має бути простим, адже rerere може просто зробити за вас все автоматично.

Таку саму тактику можна використовувати, якщо ви бажаєте тримати гілку перебазованою, щоб не доводилося мати справу з однаковими конфліктами перебазування щоразу, як ви його робите. Або якщо ви бажаєте взяти гілку, яку ви зливали та виправили купу конфліктів, а потім вирішили замість цього зробити перебазування — імовірно вам не доведеться знову виправляти всі ці конфлікти.

Ще однин приклад застосування rerere: коли ви іноді зливаєте купу незавершених тематичних гілок разом, щоб перевірити результат, як сам проект Git часто робить. Якщо тести провалились, ви можете скасувати зливання та зробити їх знову без тематичної гілки, що спричинила проблему, без необхідності знову розвʼязувати конфлікти.

Щоб увімкнути функціональність rerere, треба просто виконати це налаштування конфігурації:

$ git config --global rerere.enabled true

Також його можна ввімкнути, якщо створити директорію .git/rr-cache в окремому репозиторії, проте налаштування конфігурації ясніше, та вмикає цей функціонал глобально.

Тепер погляньмо на простий приклад, схожий на попередній. Скажімо, у нас є файл hello.rb, що виглядає так:

#! /usr/bin/env ruby

def hello
  puts 'hello world'
end

В одній гілці ми змінюємо слово “hello” на “holf”, потім в іншій гілці змінюємо “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:

$ 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 побачить конфлікт шмату (hunk) у файлі 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

Наше злиття скасовано. Тепер перебазуймо тематичну гілку.

$ 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

Отже, якщо ви робите багато повторних зливань, або бажаєте зберігати тематичну гілку у відповідності з гілкою master без численних зливань, або часто перебазовуєте, то можете увімкнути rerere, щоб трохи полегшити собі життя.