Git
Chapters ▾ 2nd Edition

3.1 Διακλαδώσεις στο Git - Οι κλάδοι με λίγα λόγια

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

Κάποιοι θεωρούν ότι το μοντέλο διακλάδωσης του Git είναι το μεγαλύτερο προτέρημά του και πάντως είναι σίγουρα κάτι που κάνει το Git να ξεχωρίζει στην κοινότητα των VCS. Γιατί είναι τόσο ξεχωριστό; Ο τρόπος με τον οποίο το Git δημιουργεί διακλαδώσεις είναι απίστευτα ελαφρύς, κάτι που καθιστά τις εργασίες διακλάδωσης σχεδόν στιγμιαίες και τη μετάβαση από τον έναν κλάδο στον άλλο εξίσου γρήγορη. Σε αντίθεση με πολλά άλλα VCS, το Git ενθαρρύνει έναν τρόπο εργασίας κατά τον οποίο διακλαδώσεις και συγχωνεύσεις γίνονται συχνά, ακόμα και πολλές φορές μέσα σε μία ημέρα. Η κατανόηση και ευχέρεια στη χρήση αυτού του χαρακτηριστικού θα μας εφοδιάσει με ένα ισχυρό και μοναδικό εργαλείο, που μπορεί να αλλάξει ολοκληρωτικά τον τρόπο με τον οποίο αναπτύσσουμε εφαρμογές.

Οι κλάδοι με λίγα λόγια

Για να κατανοήσουμε πραγματικά τον τρόπο με τον οποίο το Git υλοποιεί τις διακλαδώσεις, πρέπει πρώτα να εξετάσουμε τον τρόπο με τον οποίο το Git αποθηκεύει τα δεδομένα.

Όπως έχει ήδη αναφερθεί στην ενότητα Ξεκινώντας με το Git, το Git δεν αποθηκεύει τα δεδομένα ως μία ακολουθία αλλαγών ή διαφορών αλλά ως μία ακολουθία στιγμιότυπων (snapshots).

Όταν κάνουμε κάνουμε μία υποβολή (commit), το Git αποθηκεύει ένα αντικείμενο υποβολής που περιέχει έναν δείκτη προς το στιγμιότυπο του περιεχομένου που έχει υποβληθεί. Αυτό το αντικείμενο περιέχει επίσης το όνομα και email του συγγραφέα, το μήνυμα που έχουμε πληκτρολογήσει καθώς και δείκτες προς την υποβολή ή τις υποβολές που προηγήθηκαν ακριβώς πριν από αυτήν την υποβολή (δηλαδή, τον γονιό ή τους γονείς): όταν ένα αρχείο υποβάλλεται για πρώτη φορά, τότε δεν έχει κανέναν γονιό· μία συνηθισμένη υποβολή έχει έναν γονιό, ενώ μία υποβολή που προέκυψε από τη συγχώνευση δύο ή περισσότερων κλάδων έχει περισσότερους από έναν γονιό.

Για να το συγκεριμενοποιήσουμε λίγο, ας υποθέσουμε ότι έχουμε έναν κατάλογο που περιέχει τρία αρχεία, τα οποία έχουμε προσθέσει στο στάδιο καταχώρισης και επιτελούμε υποβολή. Κατά την προσθήκη των αρχείων στο στάδιο καταχώρισης υπολογίζονται τα αθροίσματα ελέγχου (checksums) των αρχείων (με τον αλγόριθμο SHA-1, που αναφέρθηκε στην ενότητα Ξεκινώντας με το Git), αποθηκεύονται οι συγκεκριμένες εκδόσεις των αρχείων στο αποθετήριο Git (το Git ονομάζει αυτές τις εκδόσεις blobs [1] και προσθέτει τα αθροίσματα ελέγχου στην ενδιαμέση περιοχή.

$ git add README test.rb LICENSE
$ git commit -m 'The initial commit of my project'

Όταν επιτελούμε την υποβολή, τρέχοντας την εντολή git commit, το Git υπολογίζει ένα άθροισμα ελέγχου για καθέναν υποκατάλογο (στη συγκεκριμένη περίπτωση μόνο για τον βασικό κατάλογο του έργου) και αποθηκεύει αυτά τα αντικείμενα δενδροειδούς δομής στο αποθετήριο Git. Στη συνέχεια το Git δημιουργεί ένα αντικείμενο υποβολής που περιέχει τα μεταδεδομένα και έναν δείκτη στη βάση του δένδρου του έργου, ώστε να μπορεί να επαναδημιουργήσει το στιγμιότυπο, όταν χρειαστεί.

Σε αυτό το στάδιο, το αποθετήριό μας περιέχει πέντε αντικείμενα: τρία blob για τα περιεχόμενα των αρχείων (ένα blob για κάθε αρχείο), μία δενδροειδή δομή που καταγράφει τα περιεχόμενα του καταλόγου και προσδιορίζει ποιο αρχείο αντιστοιχίζεται σε ποιο blob και ένα αντικείμενο commit που περιέχει τον δείκτη στον βασικό κατάλογο και όλα τα μεταδεδομένα της υποβολής.

Μία υποβολή και το δενδροδιάγραμμά της.
Figure 9. Μία υποβολή και το δενδροδιάγραμμά της.

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

Υποβολές και οι γονείς τους.
Figure 10. Υποβολές και οι γονείς τους.

Ένας κλάδος στο Git είναι απλά ένας δείκτης σε μία από αυτές τις υποβολές. Το προεπιλεγμένο όνομα κλάδου στο Git είναι master (κύριος κλάδος). Όσο επιτελούμε υποβολές, δημιουργούμε έναν κύριο κλάδο που δείχνει στην τελευταία υποβολή που κάναμε. Κάθε φορά που υποβάλλουμε, ο κύριος κλάδος μετατοπίζεται αυτόματα.

Note

Ο “κύριος” κλάδος (master) στο Git δεν είναι κάποιος ειδικός κλάδος. Είναι ακριβώς ίδιος με οποιονδήποτε άλλο κλάδο. Ο μόνος λόγος για τον οποίο σχεδόν κάθε αποθετήριο έχει έναν κλάδο master είναι επειδή η εντολή git init τον ονομάζει έτσι και οι περισσότεροι χρήστες του Git δεν ασχολούνται με το να τον αλλάξουν.

Ένας κλάδος και το ιστορικό υποβολών του
Figure 11. Ένας κλάδος και το ιστορικό υποβολών του

Δημιουργία νέου κλάδου

Τι γίνεται όταν δημιουργούμε έναν νέο κλάδο; Αυτό που συμβαίνει είναι ότι δημιουργείται ένα νέος δείκτης τον οποίο μπορούμε να μετακινούμε κατά βούληση. Ας πούμε ότι δημιουργούμε έναν νέο κλάδο που ονομάζεται testing. Αυτό γίνεται με την εντολή git branch:

$ git branch testing

Αυτή η εντολή δημιουργεί έναν νέο δείκτη στην ίδια υποβολή στην οποία βρισκόμαστε τώρα.

Δύο κλάδοι που δείχνουν στην ίδια ακολουθία υποβολών.
Figure 12. Δύο κλάδοι που δείχνουν στην ίδια ακολουθία υποβολών.

Πώς όμως γνωρίζει το Git σε ποιον κλάδο βρισκόμαστε τώρα; Το γνωρίζει επειδή διατηρεί έναν ειδικό δείκτη που ονομάζεται HEAD. Ας σημειωθεί ότι αυτός ο δείκτης HEAD είναι πολύ διαφορετικός από την έννοια του HEAD με την οποία είστε ενδεχομένως εξοικειωμένοι σε άλλα VCS, όπως το Subversion ή το CVS. Στο Git, αυτός είναι ένας δείκτης στον τοπικό κλάδο στον οποίο στον οποίο βρισκόμαστε αυτήν τη στιγμή. Στη συγκεκριμένη περίπτωση, βρισκόμαστε ακόμα στον κλάδο master. Η εντολή git branch απλά δημιούργησε έναν νέο κλάδο —δεν μετέβη σε αυτόν τον κλάδο.

Ο δείκτης HEAD δείχνει σε έναν κλάδο.
Figure 13. Ο δείκτης HEAD δείχνει σε έναν κλάδο.

Αυτό μπορούμε να το διαπιστώσουμε εύκολα τρέχοντας απλά την εντολή git log με την επιλογή --decorate, που παραθέτει πού δείχνουν οι δείκτες των κλάδων.

$ git log --oneline --decorate
f30ab (HEAD -> master, testing) add feature #32 - ability to add new formats to the central interface
34ac2 Fixed bug #1328 - stack overflow under certain conditions
98ca9 The initial commit of my project

Βλέπουμε τους κλάδους master και testing που βρίσκονται δίπλα στην υποβολή f30ab.

Μετάβαση από κλάδο σε κλάδο

Για να μεταβούμε σε έναν ήδη υπάρχοντα κλάδο, τρέχουμε την εντολή git checkout. Ας μεταβούμε στον νέο κλάδο testing:

$ git checkout testing

Η εντολή αυτή μετατοπίζει τον δείκτη HEAD ώστε να δείχνει στον κλάδο testing.

Ο `HEAD` δείχνει στον τρέχοντα κλάδο.
Figure 14. Ο HEAD δείχνει στον τρέχοντα κλάδο

Ποια είναι η σημασία αυτού του πράγματος; Ας κάνουμε ακόμα μία υποβολή:

$ vim test.rb
$ git commit -a -m 'made a change'
Ο κλάδος `HEAD` μετακινείται ότι γίνεται μία υποβολή.
Figure 15. Ο κλάδος HEAD μετακινείται ότι γίνεται μία υποβολή

Αυτό είναι ενδιαφέρον, διότι τώρα ο νέος μας κλάδος testing έχει προχωρήσει, αλλά ο κλάδος master ακόμα δείχνει στην υποβολή που βρισκόμασταν όταν είχαμε τρέξει την εντολή git checkout για να αλλάξουμε κλάδο. Ας επιστρέψουμε στον κλάδο master:

$ git checkout master
Ο `HEAD` μετακινείται όταν εκτελούμε `checkout`.
Figure 16. Ο HEAD μετακινείται όταν εκτελούμε checkout.

Αυτή η εντολή έκανε δύο πράγματα: Μετατόπισε τον δείκτη HEAD ώστε να ξαναδείχνει στον κλάδο master και επανέφερε τα αρχεία στον τρέχοντα κατάλογο στο στιγμιότυπο που δείχνει ο κλάδος master'. Αυτό σημαίνει επίσης ότι όποιες αλλαγές κάνουμε από αυτό το σημείο και μετά θα αποκλίνουν από την προηγούμενη έκδοση του έργου. Ουσιαστικά αναιρεί όποιες αλλαγές έχουμε κάνει στον κλάδο `testing ώστε να μπορέσουμε να κινηθούμε σε μία διαφορετική κατεύθυνση.

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

Ας κάνουμε μερικές ακόμα αλλαγές και ας τις υποβάλλουμε:

$ vim test.rb
$ git commit -a -m 'made other changes'

Τώρα το ιστορικό του έργου έχει αποκλίνει (βλ. Αποκλίνον ιστορικό). Δημιουργήσαμε έναν κλάδο, μεταβήκαμε σε αυτόν, κάναμε κάποιες αλλαγές και μετά επιστρέψαμε στον κλάδο main' και κάναμε κάποιες άλλες αλλαγές. Οι αλλαγές αυτές είναι απομονωμένες σε διαφορετικούς κλάδους: μπορούμε να μεταπηδούμε από τον έναν κλάδο στον άλλο και να τους συγχωνεύσουμε όταν είμαστε έτοιμοι να κάνουμε κάτι τέτοιο. Και όλα αυτά τα καταφέρνουμε με απλές εντολές `branch, checkout και commit.

Αποκλίνον ιστορικό.
Figure 17. Αποκλίνον ιστορικό

Αυτό μπορούμε επίσης να το δούμε εύκολα με την εντολή git log. Αν τρέξουμε την εντολή git log --oneline --decorate --graph --all θα εκτυπωθεί το ιστορικό των υποβολών στο οποίο θα φαίνεται πού βρίσκονται οι δείκτες των κλάδων μας και με ποιον τρόπο έχει αποκλίνει το ιστορικό.

$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
* f30ab add feature #32 - ability to add new formats to the
* 34ac2 fixed bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project

Επειδή ένας κλάδος στο Git είναι στην πραγματικότητα ένα αρχείο που περιέχει τους 40 χαρακτήρες του αθροίσματος ελέγχου SHA-1 της υποβολής στην οποία δείχνει, η δημιουργία και καταστροφή κλάδων είναι μία φθηνή διαδικασία. Η δημιουργία ενός κλάδου είναι τόσο γρήγορη και απλή όσο το να γράφονται 41 byte σε ένα αρχείο (40 αλφαριθμητικοί χαρακτήρες και ένας χαρακτήρας αλλαγής γραμμής).

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

Ας δούμε γιατί πρέπει να το κάνουμε αυτό.