Git
Chapters ▾ 2nd Edition

7.9 Εργαλεία του Git - Rerere

Rerere

Η λειτουργικότητα git rerere είναι ένα λίγο κρυμμένο χαρακτηριστικό. Το όνομα σημαίνει “reuse recorded resolution” (“επαναχρησιμοποίηση καταγεγραμμένης επίλυσης”) και όπως υποδηλώνει το όνομα, μας επιτρέπει να ζητήσουμε από το Git να θυμάται πώς έχουμε επιλύσει μια σύγκρουση τμημάτων κώδικα έτσι ώστε την επόμενη φορά που βλέπει την ίδια σύγκρουση, να την επιλύσει αυτόματα.

Υπάρχουν ορισμένα σενάρια στα οποία η λειτουργία αυτή μπορεί να είναι πολύ βολική. Ένα από τα παραδείγματα που αναφέρονται στην τεκμηρίωση είναι αν θέλουμε να διασφαλίσουμε ότι ένας μακρόβιος θεματικός κλάδος θα συγχωνευθεί χωρίς συγκρούσεις αλλά δεν θέλουμε να έχουμε ένα μάτσο ενδιάμεσων υποβολών συγχώνευσης. Εφόσον ενεργοποιήσουμε την rerere μπορούμε να συγχωνεύουμε περιστασιακά, να επιλύσουμε τις συγκρούσεις και στη συνέχεια να αναιρούμε τη συγχώνευση. Αν το κάνουμε αυτό συνεχώς, τότε η τελική συγχώνευση θα είναι εύκολη επειδή η rerere θα μπορεί να κάνει τα πάντα για εμάς αυτόματα.

Η ίδια τακτική μπορεί να χρησιμοποιηθεί αν θέλουμε να διατηρήσουμε έναν κλάδο επανατοποθετημένο, ώστε να μην χρειάζεται να ασχολούμαστε με τις ίδιες συγκρούσεις επανατοποθέτησης κάθε φορά που το κάνουμε. Ή αν θέλουμε να πάρουμε έναν κλάδο που συγχωνεύσαμε, στον οποίο επίσης διορθώσουμε μια μερικές συγκρούσεις και στη συνέχεια να αποφασίσαμε να τον επανατοποθετήσουμε σε αλλη βάση —πιθανότατα δεν θα χρειαστεί να επιλύσουμε όλες τις προηγούμενες συγκρούσεις ξανά.

Μια άλλη περίσταση είναι όταν συγχωνεύουμε μερικούς εν εξελίξει θεματικούς κλάδους μαζί σε ένα δοκιμαστικό κεφάλι περιστασιακά, όπως το ίδιο το έργο Git συχνά. Εάν οι δοκιμές αποτύχουν, μπορούμε να αναιρέσουμε τις συγχωνεύσεις και να τις επαναλάβουμε χωρίς τον θεματικό κλάδο εξαιτίας του οποίου απέτυχαν οι δοκιμές, χωρίς να χρειάζεται να ξαναεπιλύσουμε τις συγκρούσεις.

Για να ενεργοποιήσουμε τη λειτουργία rerere, απλά εκτελέσουμε αυτήν τη ρύθμιση παραμέτρων:

$ git config --global rerere.enabled true

Μπορούμε επίσης να την ενεργοποιήσουμε δημιουργώντας έναν κατάλογο .git/rr-cache σε ένα συγκεκριμένο αποθετήριο, αλλά η ρύθμιση της παραμέτρου με την config είναι σαφέστερη και μπορεί να γίνει καθολικά.

Τώρα ας δούμε ένα απλό παράδειγμα, παρόμοιο με το προηγούμενο. Ας υποθέσουμε ότι έχουμε ένα αρχείο που μοιάζει με αυτό:

#! /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:

$ 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), μπορούμε να χρησιμοποιήσουμε την 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') και μπορούμε να εκτελέσουμε ξανά την εντολή 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

Τώρα ας αναιρέσουμε αυτήν τη συγχώνευση και στη συνέχεια ας αλλάξουμε τη βάση του θεματικού κλάδου στον κύριο κλάδο. Μπορούμε να επαναφέρουμε τον κλάδο μας χρησιμοποιώντας την 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. Αν εξετάσουμε το αρχείο, θα δούμε ότι έχει ήδη επιλυθεί, δεν υπάρχουν επισημάνσεις σύγκρουσης συγχώνευσης.

$ cat hello.rb
#! /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

Μπορούμε επίσης να ξαναδημιουργήσουμε την κατάσταση σύγκρουσης του αρχείου με την εντολή 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

Είδαμε ένα παράδειγμα αυτού στην ενότητα Συγχωνεύσεις για προχωρημένους. Προς το παρόν, όμως, ας την ξαναεπιλύσουμε τρέχοντας την rerere ξανά:

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

def hello
  puts 'hola mundo'
end

Έχουμε ξαναεπιλύσει το αρχείο αυτόματα χρησιμοποιώντας την αποθηκευμένη επίλυση rerere. Τώρα μπορούμε να τρέξουμε add και να συνεχίσουμε την αλλαγή βάσης για να την ολοκληρώσουμε.

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

Έτσι, εάν κάνουμε πολλές συγχωνεύσεις ή θέλουμε o θεματικός κλάδος μας να συμβαδίζει με τον κύριο κλάδο μας χωρίς να έχουμε πάμπολλες συγχωνεύσεις, μπορούμε είτε να κάνουμε συχνά αλλαγές βάσης, είτε να ενεργοποιήσουμε την rerere για να κάνουμε πιο εύκολη τη ζωή μας.