A git rerere: nem kell kétszer megoldanod ugyanazt a merge-konfliktust

A git rerere megjegyzi, hogyan oldottál fel egy merge- vagy rebase-konfliktust, és legközelebb, ha ugyanaz a hunk jön elő, automatikusan visszaadja a megoldást. 2006 óta ott van minden git telepítésben, csak alapból ki van kapcsolva.

Ha már rebase-eltél hosszabb életű feature branchet mozgó master fölé, ismerős a jelenet: minden egyes rebase-nél ugyanaz a konfliktus jön elő ugyanabban a config fájlban vagy lock file-ban, és minden egyes alkalommal kézzel oldod fel — pontosan úgy, mint legutóbb. A git ezt a problémát 2006 óta kezeli egy beépített, de alapból kikapcsolt funkcióval: a rerere-rel.

A név a „reuse recorded resolution” rövidítése. A parancs megjegyzi, hogyan oldottál fel egy konfliktust, és legközelebb — ha ugyanaz a konfliktus jön elő újra — automatikusan visszaadja a megoldást, anélkül hogy megkérdezné. Ez nem egy új git kliens funkció, hanem egy 20 éve létező, natív mechanizmus, amit a legtöbb fejlesztő soha nem kapcsol be.

Diagram: a git rerere folyamata — az első rebase-nél kézzel feloldott konfliktus a .git/rr-cache alá kerül preimage és postimage formájában, majd egy későbbi, azonos konfliktust dobó rebase-nél a git automatikusan visszaadja ezt a megoldást.

Mit csinál a motorháztető alatt

Amikor egy merge vagy rebase konfliktust dob, és a rerere.enabled be van kapcsolva, a git elmenti a konfliktusos fájl állapotát (a `<<<<<<<`/`=======`/`>>>>>>>` jelölőkkel) a .git/rr-cache alá, egy a tartalom hash-éből képzett könyvtárba, preimage néven. Amikor kézzel feloldod és git add-olod a fájlt, a végeredmény postimage-ként kerül mellé.

Legközelebb, ha a git ugyanazt a konfliktusos hunk-ot generálja automerge közben, a rerere egy three-way merge-et futtat a régi konfliktus, a régi megoldás és az új konfliktus között. Ha ez tisztán lefut, a megoldás bekerül a munkafába — nincs kézi szerkesztés. Ezt a mechanizmust részletesen leírja a git-rerere hivatalos dokumentációja, ami külön kiemeli, hogy a git merge és a git commit automatikusan meghívja a rerere-t, a git rebase pedig ugyanerre a mechanizmusra épül konfliktusfeloldásnál.

Bekapcsolás

git config --global rerere.enabled true
git config --global rerere.autoUpdate true   # opcionális, lásd lentebb

Van egy furcsa alapértelmezés: a git-config dokumentáció szerint a rerere automatikusan bekapcsolt állapotban van, ha a repóban már létezik rr-cache könyvtár a $GIT_DIR alatt — tehát ha valaha manuálisan futott a repóban egy git rerere, a funkció onnantól „magától” aktív marad, explicit config nélkül is. Ez megtévesztő tud lenni klónozott repóknál, ahol nem is sejtenéd, hogy be van kapcsolva.

Élesben: kétszer ugyanaz a rebase-konfliktus

Egy egyszerű teszt: egy feature branch és a master is módosítja ugyanazt a sort egy fájlban. Az első rebase konfliktust dob:

$ git config rerere.enabled true
$ git rebase master
Rebasing (1/2)
CONFLICT (content): Merge conflict in config.txt
Recorded preimage for 'config.txt'

$ cat config.txt
sor1
<<<<<<< HEAD
sor2-main
=======
sor2-feature
>>>>>>> feature: sor2 modositas
sor3

# kézi feloldás után:
$ git add config.txt
$ git rebase --continue
Recorded resolution for 'config.txt'.

A „Recorded resolution” sor a rerere naplózása — innentől a .git/rr-cache alatt egy preimage/postimage pár őrzi, mit oldottál fel és mire. Ha most ugyanezt a rebase-t egy másik branchen (vagy a régi branch-tipp visszaállítása után) újra lefuttatod, a git felismeri a konfliktust, és magától visszaadja a korábbi megoldást:

$ git rebase master
Rebasing (1/2)
CONFLICT (content): Merge conflict in config.txt
Staged 'config.txt' using previous resolution.

$ git rebase --continue
[detached HEAD ...] feature: sor2 modositas
Rebasing (2/2)
Successfully rebased and updated refs/heads/feature.

A „Staged ‘config.txt’ using previous resolution” sor a lényeg: a fájl már a korábbi, kézzel jóváhagyott tartalommal van indexelve, nem kell újra megnyitni és átírni. Ezt saját magam futtattam le lokálisan (git 2.39), pontosan ez a kimenet jön.

Az autoUpdate nem jelenti azt, hogy nincs megállás

Van egy elterjedt félreértés, hogy a rerere.autoUpdate bekapcsolása után a rebase teljesen csendben, megállás nélkül végigfut. Nem így van. A beállítás csak azt intézi, hogy a felismert megoldás automatikusan git add-olva legyen — a rebase minden korábban konfliktusos commitnál továbbra is megáll, és neked kell kiadnod a git rebase --continue parancsot, csak nem kell előtte kézzel szerkesztened és stage-elned a fájlt. Ez a viselkedés is kiolvasható a dokumentációból: alapértelmezés szerint (autoUpdate=false) a rerere „csak” a munkafába írja ki a megoldást, add-olni neked kell; true esetén ezt is elvégzi helyetted, de a rebase állapotgépe ugyanúgy egy megálló pontnak kezeli a commitot.

A hasznos alparancsok

  • git rerere status — mely fájlak konfliktusát fogja rögzíteni a rerere a jelenlegi állapotból.
  • git rerere diff — mit fog rögzíteni pontosan (a preimage és a jelenlegi munkafa különbsége).
  • git rerere remaining — mely konfliktusokat nem tudta automatikusan feloldani (pl. submodule-konfliktusokat sosem, ezt a dokumentáció is kiemeli).
  • git rerere forget <path> — töröld a rögzített megoldást egy adott fájlra, ha rosszul lett elmentve.
  • git rerere gc — a régi bejegyzések takarítása. Alapértelmezésben a fel nem oldott konfliktusok 15 napig, a feloldottak 60 napig maradnak meg (gc.rerereUnresolved és gc.rerereResolved).

Ahol elakadhatsz

A .git/rr-cache nem verziózott tartalom — nem kerül be a commitokba, nem megy át push/pull-on, tehát a csapat egyik tagja által feloldott konfliktus nem „öröklődik” automatikusan a másik klónjába. Ha egy csapat közösen dolgozik egy hosszú életű branchen, és mindenki ugyanazokkal a rebase-konfliktusokkal küzd, ez önmagában kevés. Erre van egy kevésbé ismert segédeszköz a git forrásában: a contrib/rerere-train.sh szkript, ami a git history már meglévő merge-commitjaiból tölti fel előre a rerere cache-t — vagyis retroaktívan „megtanítja” a rerere-nek a múltbeli konfliktusfeloldásokat, mielőtt bármi újat rebase-elnél.

A másik gyakori csapda: a rerere kizárólag a szövegbeli konfliktusjelölőkre (<<<<<<<, =======, >>>>>>>) hagyatkozik a hunk felismeréséhez. Ha egy fájl már eleve tartalmaz ilyen mintázatú sorokat — tipikusan a git saját dokumentációja, ami konfliktusjelölőket magyaráz —, a rerere összezavarodhat. Erre való a conflict-marker-size gitattribútum, ami megnöveli a jelölők hosszát a szokásos 7 karakterről; a git saját repója pontosan ezt teszi a Documentation/git-merge.adoc fájlra, 32 karakteres jelölőket kérve.

És a dokumentáció maga is figyelmeztet: a rerere a munkafába írja ki a megoldást, de az indexet érintetlenül hagyja a végső ellenőrzés előtt — vagyis még ha automatikusan is oldódik fel egy konfliktus, érdemes git diff-fel átnézni, mielőtt --continue-olsz. Egy azonos szövegű hunk ugyanis nem garantáltan azonos jelentésű minden kontextusban.

Ahol ez tényleg megéri: hosszú életű feature branch, amit rendszeresen rebase-elsz a master mögé, package-lock vagy generált fájlok ismétlődő ütközései, vagy épp több párhuzamos AI-agent branch, amik ugyanazt a konfliktust generálják újra és újra egy megosztott törzs ellen. Egy git config rerere.enabled true és soha többé nem kell kétszer megoldanod ugyanazt a problémát.

Források

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük