Git
Chapters ▾ 2nd Edition

5.3 Κατανεμημένο Git - Συντήρηση ενός έργου

Συντήρηση ενός έργου

Εκτός από το να γνωρίζουμε πώς να συμβάλλουμε αποτελεσματικά σε ένα έργο, πιθανότατα θα χρειαστεί να ξέρουμε πώς να διαχειριζόμαστε ένα αποθετήριο. Αυτό μπορεί να συνίσταται στην αποδοχή και εφαρμογή των επιθεμάτων που δημιουργούνται με το format-patch και στέλνονται με e-mail σε εμάς ή στην ενσωμάτωση αλλαγών σε απομακρυσμένους κλάδους για αποθετήρια που έχουμε προσθέσει ως απομακρυσμένα στο έργο μας. Είτε διαχειριζόμαστε ένα τυπικό αποθετήριο είτε θέλουμε να βοηθήσουμε επαληθεύοντας ή εγκρίνοντας επιθέματα, πρέπει να ξέρουμε πώς να δεχόμαστε την εργασία με τρόπο που είναι κατά το δυνατό ξεκάθαρος στους άλλους συνεισφέροντες και βιώσιμος από εμάς μακροπρόθεσμα.

Εργασία σε θεματικούς κλάδους

Όταν σκέφτομαστε να ενοποιήσουμε ένα νέο έργο, είναι γενικά καλή ιδέα να το δοκιμάσουμε σε έναν θεματικό κλάδο – έναν προσωρινό κλάδο ειδικά σχεδιασμένο για να δοκιμάσουμε αυτήν την εργασία. Με αυτόν τον τρόπο, είναι εύκολο να τροποποιήσουμε επίθεμα ξεχωριστά και να το παρατήσουμε αν δεν λειτουργεί μέχρι να έχουμε χρόνο να ξανασχοληθούμε μαζί του. Αν δημιουργήσουμε ένα απλό όνομα κλάδου με βάση το θέμα της εργασίας που πρόκειται να δοκιμάσουμε, όπως ruby_client ή κάτι παρόμοια περιγραφικό, μπορούμε εύκολα να το θυμόμαστε, ακόμα κι αν χρειαστέί να το εγκαταλείψουμε για αρκετό καιρό και να επιστρέψουμε αργότερα. Οι διαχειριστές έργων Git συνηθίζουν επίσης να οργανώνουν αυτούς τους κλάδους σε χώρους ονομάτων (namespaces) —όπως sc/ruby_client, όπου το sc είναι συντομογραφία για τον προγραμματιστή που συνεισέφερε την εργασία. Όπως θα θυμόμαστε, μπορούμε να δημιουργήσουμε τον κλάδο να διακλαδίζεται από τον κύριο κλάδο μας ως εξής:

$ git branch sc/ruby_client master

Εναλλακτικά, αν θέλουμε να μεταβούμε σε αυτόν αμέσως, μπορούμε να χρησιμοποιήσουμε την checkout -b:

$ git checkout -b sc/ruby_client master

Τώρα είμαστε έτοιμοι να προσθέσουμε τη συνεισφορά μας σε αυτόν τον θεματικό κλάδο και να προσδιορίσουμε αν θέλουμε να τον συγχωνεύσουμε στους μακροβιότερους κλάδους μας.

Εφαρμογή επιθεμάτων από e-mail

Αν λάβουμε επίθεμα μέσω e-mail και πρέπει να το ενσωματώσουμε στο έργο μας, πρέπει να το εφαρμόσουμε στον θεματικό κλάδο για να το αξιολογήσουμε. Υπάρχουν δύο τρόποι για να εφαρμόσουμε ένα επίθεμα που λάβαμε με e-mail: με git apply ή με git am.

Εφαρμογή επιθέματος με git apply

Εάν λάβαμε το επίθεμα από κάποιον που το δημιούργησε με την εντολή git diff ή diff του Unix (κάτι που δεν συνιστάται, όπως δούμε στην επόμενη ενότητα), μπορούμε να το εφαρμόσουμε με την εντολή git apply. Αν υποθέσουμε ότι έχουμε αποθηκεύσει το επίθεμα στον φάκελο /tmp/patch-ruby-client.patch, μπορούμε να το εφαρμόσουμε ως εξής:

$ git apply /tmp/patch-ruby-client.patch

Αυτή η εντολή τροποποιεί τα αρχεία στον κατάλογο εργασίας μας. Είναι σχεδόν πανομοιότυπη με την εκτέλεση της εντολής patch -p1 για την εφαρμογή του επιθέματος, αν και είναι πιο παρανοϊκή και δέχεται λιγότερες ασαφείς αντιστοιχίσεις από την patch. Επίσης, διαχειρίζεται προσθήκες, διαγραφές και μετονομασίες αρχείων εφόσον περιγράφονται στη μορφή git diff, κάτι που δεν κάνει η patch. Τέλος, η git apply είναι ένα μοντέλο “όλα ή τίποτα” (“apply all or abort all”) όπου είτε όλες οι αλλαγές εφαρμόζονται είτε καμία, ενώ η patch μπορεί να εφαρμόσει μερικώς επιθέματα αφήνοντας τον κατάλογο εργασίας μας σε μία περίεργη κατάσταση. Η git apply είναι γενικά πολύ πιο συντηρητική από την patch. Δεν θα δημιουργήσει αυτόματα υποβολή για μας —μετά την εκτέλεση, θα πρέπει να βάλουμε τις αλλαγές στο στάδιο καταχώρισης και να τις υποβάλουμε οι ίδιοι.

Μπορούμε επίσης να χρησιμοποιήσουμε την git apply για να διαπιστώσουμε αν επίθεμα εφαρμόζεται καθαρά πριν δοκιμάσουμε την εφαρμογή του –μπορούμε να εκτελέσουμε το git apply --check με το επίθεμα:

$ git apply --check 0001-seeing-if-this-helps-the-gem.patch
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply

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

Εφαρμογή επιθέματος με την git am

Εάν ο συνεισφέρων είναι χρήστης του Git και ήταν αρκετά καλός για να χρησιμοποιήσει την εντολή format-patch για να δημιουργήσει το επίθεμα, τότε η εργασία μας είναι ευκολότερη διότι το επίθεμα περιέχει πληροφορίες για τον συγγραφέα και μήνυμα υποβολής. Αν είναι δυνατό, καλο είναι να ενθαρρύνουμε τους συνεργάτες μας να χρησιμοποιούν την format-patch αντί για την diff για να δημιουργούν επιθέματα για μας. Θα πρέπει να χρησιμοποιούμε την git apply μόνον για επιθέματα παλιού τύπου (legacy).

Για να εφαρμόσουμε ένα επίθεμα που δημιουργείται με την format-patch, χρησιμοποιούμε την git am. Από τεχνικής άποψης, η git am είναι κατασκευασμένη για να διαβάζει ένα αρχείο mbox, που είναι μία απλή μορφή αρχείου κειμένου για την αποθήκευση ενός ή περισσοτέρων μηνυμάτων ηλεκτρονικού ταχυδρομείου σε ένα αρχείο κειμένου. Φαίνεται κάπως έτσι:

From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] add limit to log function

Limit log functionality to the first 20

Αυτή είναι η αρχή της εξόδου της εντολής format-patch που είδαμε στην προηγούμενη ενότητα. Είναι επίσης μια έγκυρη μορφή mbox ηλεκτρονικού μηνύματος. Εάν κάποιος μας έχει στείλει ηλεκτρονικά το επίθεμα χρησιμοποιώντας την git send-email και το κατεβάσουμε σε μορφή mbox, τότε μπορούμε να κατευθύνουμε την git am στο αρχείο mbox και αυτό θα αρχίσει να εφαρμόζει όλα τα επιθέματα που βλέπει. Εάν τρέχουμε ένα πρόγραμμα-πελάτη ηλεκτρονικής αλληλογραφίας που μπορεί να αποθηκεύσει πολλά μηνύματα ηλεκτρονικού ταχυδρομείου σε μορφή mbox, μπορούμε να αποθηκεύσουμε ολόκληρες σειρές επιθεμάτων σε ένα αρχείο και στη συνέχεια να χρησιμοποιήσουμε την git am για να εφαρμόσουμε το καθένα ξεχωριστά.

Ωστόσο, αν κάποιος χρήστης ανέβασε ένα επίθεμα που δημιουργήθηκε με την format-patch σε ένα σύστημα εισιτηρίων ή κάτι παρόμοιο, μπορούμε να αποθηκεύσουμε το αρχείο τοπικά και να περάσουμε το αρχείο που αποθηκεύσαμε στην git am για να το εφαρμόσουμε:

$ git am 0001-limit-log-function.patch
Applying: add limit to log function

Μπορούμε να δούμε ότι επίθεμα εφαρμόστηκε καθαρά και αυτόματα δημιουργήθηκε η νέα υποβολή για μας. Οι πληροφορίες του συντάκτη λαμβάνονται από τις κεφαλίδες From: και Date: του ηλεκτρονικού μηνύματος και το μήνυμα της υποβολής λαμβάνεται από το Subject: και το σώμα του μηνύματος (πριν από την ενημερωμένη έκδοση κώδικα). Για παράδειγμα, εάν αυτή η ενημερωμένη έκδοση κώδικα εφαρμόστηκε από το παραπάνω παράδειγμα mbox, η υποβολή που θα δημιουργούνταν θα φαινόταν κάπως έτσι:

$ git log --pretty=fuller -1
commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0
Author:     Jessica Smith <jessica@example.com>
AuthorDate: Sun Apr 6 10:17:23 2008 -0700
Commit:     Scott Chacon <schacon@gmail.com>
CommitDate: Thu Apr 9 09:19:06 2009 -0700

   add limit to log function

   Limit log functionality to the first 20

Οι πληροφορίες στο πεδίο Commit υποδεικνύουν το άτομο που εφάρμοσε το επίθεμα και την ώρα που έγινε η εφαρμογή. Οι πληροφορίες στο πεδίο Author υποδεικνύουν το άτομο που αρχικά δημιούργησε το επίθεμα και πότε δημιουργήθηκε για πρώτη φορά.

Αλλά είναι πιθανό ότι το επίθεμα δεν θα εφαρμοστεί καθαρά. Ίσως ο κύριος κλάδος μας έχει αποκλίνει πολύ από τον κλάδο από τον οποίο έχει διακλαδώθηκε αυτό το επίθεμα ή το επίθεμα εξαρτάται από ένα άλλο επίθεμα λογισμικού που δεν έχουμε εφαρμόσει ακόμα. Σε αυτήν την περίπτωση, η διαδικασία με την git am θα αποτύχει και θα μας ρωτήσει τι θέλουμε να κάνουμε:

$ git am 0001-seeing-if-this-helps-the-gem.patch
Applying: seeing if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Patch failed at 0001.
When you have resolved this problem run "git am --resolved".
If you would prefer to skip this patch, instead run "git am --skip".
To restore the original branch and stop patching run "git am --abort".

Αυτή η εντολή τοποθετεί σημάνσεις σύγκρουσης σε όλα τα αρχεία με τα οποία αντιμετωπίζει προβλήματα, όπως μια σύγκρουση συγχώνευσης ή αλλαγής βάσης. Μπορούμε να επιλύσουμε αυτό το πρόβλημα λίγο-πολύ με τον ίδιο τρόπο —επεξεργαζόμαστε το αρχείο για να επιλύσουμε τη σύγκρουση, τοποθετούμε το νέο αρχείο στο στάδιο καταχώρισης και στη συνέχεια να εκτελούμε την git am --resolved για να συνεχίσουμε στο επόμενο επίθεμα:

$ (fix the file)
$ git add ticgit.gemspec
$ git am --resolved
Applying: seeing if this helps the gem

Εάν θέλουμε το Git να δοκιμάσει να επιλύσει τη σύγκρουση με λίγο πιο έξυπνο τρόπο, μπορούμε να περάσουμε την επιλογή -3 στην git am, η οποία θα κάνει το Git να επιχειρήσει μια τριμερή συγχώνευση. Αυτή η επιλογή δεν είναι ενεργοποιημένη εκ προεπιλογής, επειδή δεν λειτουργεί εφόσον η υποβολή στην οποία λέει το επίθεμα ότι βασίζεται δεν είναι στο αποθετήριό μας. Αν έχουμε αυτήν την υποβολή —για παράδειγμα αν το επίθεμα βασίστηκε σε δημόσια υποβολή— τότε η επιλογή -3 είναι γενικά πολύ πιο έξυπνη όσον αφορά στην εφαρμογή ενός συγκρουόμενου επιθέματος λογισμικού:

$ git am -3 0001-seeing-if-this-helps-the-gem.patch
Applying: seeing if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
No changes -- Patch already applied.

Σε αυτήν την περίπτωση, αυτό το επίθεμα είχε ήδη εφαρμοστεί. Χωρίς την επιλογή -3, θα έμοιαζε με σύγκρουση.

Αν εφαρμόζουμε μια σειρά από επιθέματα από ένα mbox, μπορούμε επίσης να εκτελέσουμε την εντολή am σε διαδραστική (interactive) λειτουργία, η οποία σταματά σε κάθε επίθεμα που βρίσκει και ρωτά αν θέλουμε να το εφαρμόσουμε:

$ git am -3 -i mbox
Commit Body is:
--------------------------
seeing if this helps the gem
--------------------------
Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all

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

Όταν όλα τα επιθέματα για το θέμα μας εφαρμοστούν και υποβληθούν στον κλάδο μας, μπορούμε να επιλέξουμε εάν και πώς θα τα ενσωματώσουμε σε έναν μακροβιότερο κλάδο.

Checking Out απομακρυσμένους κλάδους

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

Για παράδειγμα, εάν η Jessica μας στείλει ένα e-mail που λέει ότι έχει ένα εξαιρετικό νέο χαρακτηριστικό στον κλάδο ruby-client του αποθετηρίου της, μπορούμε να το δοκιμάσουμε προσθέτοντας το αποθετήριο ως απομακρυσμένο και μεταβαίνοντας σε αυτόν τον κλάδο τοπικά:

$ git remote add jessica git://github.com/jessica/myproject.git
$ git fetch jessica
$ git checkout -b rubyclient jessica/ruby-client

Εάν η Jessica ξαναστείλει e-mail αργότερα με κάποιον άλλο κλάδο που περιέχει άλλο ένα εξαιρετικό χαρακτηριστικό, μπορούμε να τον ανακτήσουμε (fetch) και να τον κάνουμε checkout τοπικά διότι έχουμε ήδη το απομακρυσμένο αποθετήριο εγκατεστημένο.

Αυτό είναι ιδιαίτερα χρήσιμο αν συνεργαζόμαστε με ένα άτομο με συχνά. Εάν κάποιος συνεισφέρει μόνον ένα επίθεμα μία στις τόσες, τότε η αποδοχή του μέσω email είναι ενδεχομένως λιγότερο χρονοβόρα από το να πρέπει όλοι να τρέχουν το δικό τους διακομιστή και να να προσθαφαιρούν απομακρυσμένους διακομιστές συνεχώς για να πάρουν μιά χούφτα επιθέματα. Επίσης, είναι μάλλον απίθανο να θέλουμε να έχουμε εκατοντάδες απομακρυσμένους διακομιστές, έναν για καθένα που συνεισφέρει καναδυό επιθέματα μια στις τόσες. Ωστόσο, script και φιλοξενούμενες υπηρεσίες μπορεί να το κάνουν ευκολότερο – αυτό εξαρτάται σε μεγάλο βαθμό από τον τρόπο με τον οποίο εμείς και οι συνεισφέροντες αναπτύσσουμε τον κώδικά μας.

Το άλλο πλεονέκτημα αυτής της προσέγγισης είναι ότι έχουμε και το ιστορικό των υποβολών. Παρόλο που ίσως έχουμε εύλογα ζητήματα συγχώνευσης, γνωρίζουμε πού βρίσκεται η σχετική εργασία τους στο ιστορικό μας· μια κατάλληλη τριμερής συγχώνευση είναι η προτιμότερη επιλογή από τη χρήση της επιλογής -3 με την ελπίδα ότι το επίθεμα δημιουργήθηκε από μια δημόσια υποβολή στην οποία έχουμε πρόσβαση.

Εάν δεν συνεργαζόμαστε συχνά με ένα άτομο, αλλά εξακολουθούμε να θέλουμε να έλξουμε από αυτόν με αυτόν τον τρόπο, μπορούμε να δώσουμε τη διεύθυνση URL του απομακρυσμένου αποθετηρίου στην εντολή git pull. Αυτό κάνει μία και μοναδική έλξη και δεν αποθηκεύει τη διεύθυνση URL ως απομακρυσμένη αναφορά:

$ git pull https://github.com/onetimeguy/project
From https://github.com/onetimeguy/project
 * branch            HEAD       -> FETCH_HEAD
Merge made by recursive.

Προσδιορισμός του τι έχει εισαχθεί

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

Συχνά είναι χρήσιμο να πάρουμε μια ανασκόπηση όλων των υποβολών που βρίσκονται σε αυτόν τον κλάδο, αλλά δεν βρίσκονται στον κύριο κλάδο μας. Μπορούμε να εξαιρέσουμε υποβολές στον κύριο κλάδο μας προσθέτοντας την επιλογή --not πριν από το όνομα του κλάδου. Αυτό είναι το ίδιο με τη μορφή master..contrib που χρησιμοποιήσαμε προηγουμένως. Για παράδειγμα, εάν ο συνεισφέρων μας στείλει δύο επιθέματα και δημιουργήσουμε έναν κλάδο με το όνομα contrib και εφαρμόσουμε αυτά τα επιθέματα εκεί, μπορούμε να τρέξουμε:

$ git log contrib --not master
commit 5b6235bd297351589efc4d73316f0a68d484f118
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Oct 24 09:53:59 2008 -0700

    seeing if this helps the gem

commit 7482e0d16d04bea79d0dba8988cc78df655f16a0
Author: Scott Chacon <schacon@gmail.com>
Date:   Mon Oct 22 19:38:36 2008 -0700

    updated the gemspec to hopefully work better

Για να δούμε τι αλλαγές εισάγει κάθε υποβολή, ας θυμηθούμε ότι μπορούμε να περάσουμε την επιλογή -p στο git log και θα προσαρτήσει το (αποτέλεσμα της) diff που εισήχθη σε κάθε υποβολή.

Για να δούμε το πλήρες (αποτέλεσμα της) diff που θα παίρναμε αν συγχωνεύαμε αυτόν τον θεματικό κλάδο με ένα άλλο κλάδο, ίσως χρειαστεί να χρησιμοποιήσουμε ένα περίεργο τέχνασμα για να έχουμε τα σωστά αποτελέσματα. Κάποιος θα σκεφτόταν ίσως να τρέξει αυτό:

$ git diff master

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

Αν ο κλάδος master είναι άμεσος πρόγονος του θεματικού κλάδου μας, αυτό δεν είναι πρόβλημα· αλλά αν τα δύο ιστορικά έχουν αποκλίνει, η διαφορά θα μοιάζει σαν να προσθέτουμε όλα τα νέα στοιχεία στον θεματικό κλάδο και να καταργούμε ό,τι υπάρχει μόνον στον κλάδο master.

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

Από τεχνικής άποψης, αυτό μπορούμε να το καταφέρουμε αν εντοπίσουμε τον κοινό πρόγονο και στη συνέχεια τρέξουμε τη διαφορά diff σε αυτό:

$ git merge-base contrib master
36c7dba2c95e6bbb78dfa822519ecfec6e1ca649
$ git diff 36c7db

Ωστόσο, αυτό δεν είναι πολύ βολικό, γι' αυτό το Git παρέχει μια άλλη συντομογραφία για να κάνει το ίδιο πράγμα: τη σύνταξη με τις τρεις τελείες. Στο πλαίσιο της εντολής diff, μπορούμε να βάλουμε τρεις τελείες μετά από ένα άλλο κλάδο για να κάνουμε ένα diff μεταξύ της τελευταίας υποβολής του κλάδου που βρισκόμαστε και του κοινού προγόνου της με έναν άλλο κλάδο:

$ git diff master...contrib

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

Ενοποίηση συνεισφερθείσας εργασίας

Όταν όλη δουλειά στον θεματικό κλάδο μας είναι έτοιμη να ενσωματωθεί σε ένα πιο κεντρικό κλάδο, το ερώτημα που ανακύπτει είναι: πώς το κάνουμε. Επιπλέον, ποια συνολική ροή εργασίας θέλουμε να χρησιμοποιήσουμε για να διαχειριστούμε το έργο μας; Έχουμε αρκετές επιλογές και στη συνέχεια θα καλύψουμε μερικές από αυτές.

Συγχώνευση ροών εργασίας

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

Ιστορικό με θεματικούς κλάδους.
Figure 73. ιστορικό με θεματικούς κλάδους.
Ιστορικό μετά από συγχώνευση των θεματικών κλάδων.
Figure 74. ιστορικό μετά από συγχώνευση θεματικών κλάδων.

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

Αν έχουμε ένα πιο σημαντικό έργο, ίσως θέλουμε να χρησιμοποιήσουμε έναν κύκλο συγχώνευσης δύο φάσεων. Σε αυτό το σενάριο έχουμε δύο μακρόβιους κλάδους, master και develop, στους οποίους καθορίζουμε ότι ο master ενημερώνεται μόνο όταν δημιουργείται μία πολύ σταθερή έκδοση και όλος ο νέος κώδικας είναι ενσωματωμένος στον κλάδο develop. Ωθούμε και τους δύο αυτούς κλάδους τακτικά σε ένα δημόσιο αποθετήριο. Κάθε φορά που έχουμε έναν νέο θεματικό κλάδο να συγχωνεύσουμε (Πριν από τη συγχώνευση θεματικού κλάδου.), τον συγχωνεύουμε στον κλάδο develop (Μετά τη συγχώνευση θεματικού κλάδου.)· τότε, όταν επισημαίνουμε μια έκδοση (release), μετακινούμε γρήγορα τον master σε όποιον κλάδο βρίσκεται το πλέον ευσταθής κλάδος develop (Μετά τη δημοσιοποίηση έκδοσης.).

Πριν από τη συγχώνευση θεματικού κλάδου.
Figure 75. Πριν από τη συγχώνευση θεματικού κλάδου.
Μετά τη συγχώνευση θεματικού κλάδου.
Figure 76. Μετά τη συγχώνευση θεματικού κλάδου.
Μετά τη δημοσιοποίηση έκδοσης.
Figure 77. Μετά τη δημοσιοποίηση έκδοσης.

Με αυτόν τον τρόπο, όταν κάποιος κλωνοποιεί το αποθετήριο του έργου μας, μπορεί είτε να μεταβεί στον κλάδο master, να χτίσει την πιο πρόσφατη ευσταθή έκδοση και να συμβαδίζει με αυτήν εύκολα, είτε να μεταβεί στον κλάδο develop, που περιέχει τις πιο πρόσφατες εξελίξεις. Μπορούμε επίσης να επεκτείνουμε αυτό το μοντέλο και να έχουμε έναν κλάδο ολοκλήρωσης integrate στο οποίο όλες οι εργασίες συγχωνεύονται. Στη συνέχεια, όταν το codebase σε αυτόν τον κλάδο είναι ευσταθές και περνάει τις δοκιμές, μπορούμε να το συγχωνεύσουμε σε έναν κλάδο develop· και όταν και αυτός έχει αποδειχθεί ευσταθής για κάποιο χρονικό διάστημα, τον ταχυπροωθούμε στον κύριο κλάδο μας.

Ροές εργασίας με συγχωνεύσεις μακρόβιων κλάδων

Το έργο Git έχει τέσσερις μακρόβιους κλάδους: τους master,` next` και pu (proposed updates —προτεινόμενες ενημερώσεις) για νέες εργασίες και maint για τους κώδικες επαναφοράς συντήρησης. Όταν εισάγονται νέες εργασίες από συνεργάτες, συλλέγονται σε θεματικούς κλάδους στο αποθετήριο του διαχειριστή με τρόπο παρόμοιο με αυτόν που έχουμε περιγράψει (βλ. Διαχείριση περίπλοκης ακολουθίας παράλληλα συνεισφερθέντων θεματικών κλάδων.). Σε αυτό το σημείο, τα θέματα αξιολογούνται για να διαπιστωθεί αν είναι ασφαλή και έτοιμα για κατανάλωση ή εάν χρειάζονται περισσότερη δουλειά. Εάν είναι ασφαλή, συγχωνεύονται στον κλάδο next και αυτός ο κλάδος ωθείται, ώστε όλοι να μπορούν να δοκιμάσουν τα θέματα που ενσωματώθηκαν.

Διαχείριση περίπλοκης ακολουθίας παράλληλα συνεισφερθέντων θεματικών κλάδων.
Figure 78. Διαχείριση περίπλοκης ακολουθίας παράλληλα συνεισφερθέντων θεματικών κλάδων.

Εάν τα θέματα εξακολουθούν να χρειάζονται κάποια εργασία, συγχωνεύονται με το pu. Όταν διαπιστωθεί ότι είναι τελείως ευσταθή, τα θέματα επανασυγχωνεύονται στον master και στη συνέχεια ξαναχτίζονται από τα θέματα που ήταν στον κλάδο next, αλλά δεν είχαν ακόμα προαχθεί στον κλάδο master. Αυτό σημαίνει ότι ο κλάδος master σχεδόν πάντοτε κινείται προς τα εμπρός, ο next επανατοποθετείται περιστασιακά και ο pu επανατοποθετείται ακόμα πιο συχνά:

Η συγχώνευση ενσωμάτωσε θεματικούς κλάδους σε μακρόβιους ενοποιητικούς κλάδους.
Figure 79. Η συγχώνευση ενσωμάτωσε θεματικούς κλάδους σε μακρόβιους ενοποιητικούς κλάδους.

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

Ροές εργασίας με αλλαγή βάσης και ανθολόγηση

Άλλοι διαχειριστές προτιμούν να επανατοποθετούν ή να ανθολογούν (cherry-pick) τις συνεισφορές στην κορυφή του κύριου κλάδου τους, αντί να τις συγχωνεύουν, για να διατηρήσουν ένα κατά βάση γραμμικό ιστορικό. Όταν εργαζόμαστε σε έναν θεματικό κλάδο και έχουμε αποφασίσει ότι θέλουμε να τον ενσωματώσουμε, μεταβαίνουμε σε αυτόν και εκτελούμε την εντολή rebase για να ξαναχτίσουμε τις αλλαγές με νέα βάση τον τρέχοντα κύριο κλάδο μας (ή τον κλάδο develop). Αν αυτό λειτουργεί καλά, τότε μπορούμε να ταχυπροωθήσουμε τον κύριο κλάδο μας οπότε θα καταλήξουμε με ένα γραμμικό ιστορικό έργου.

Ο άλλος τρόπος για να μετακινήσουμε εργασία που εισάγεται από τον ένα κλάδο στον άλλο είναι η ανθολόγηση (cherry-pick). Η ανθολόγηση στο Git είναι σαν μια αλλαγή βάσης μίας μόνο υποβολής. Παίρνει το επίθεμα που εισήχθη σε μια υποβολή και προσπαθεί να το ξαναεφαρμόσει στον κλάδο στον οποίο βρισκόμαστε αυτήν τη στιγμή. Η ανθολόγηση είναι χρήσιμη εάν έχουμε αρκετές υποβολές σε έναν θεματικό κλάδο και θέλουμε να ενσωματώσουμε μόνο μία από αυτές ή εάν έχουμε μόνο μία υποβολή σε έναν θεματικό κλάδο και προτιμάμε να την ανθολογήσουμε αντί να αλλάξουμε τη βάση της. Για παράδειγμα, ας υποθέσουμε ότι έχουμε ένα έργο που μοιάζει με αυτό:

Παράδειγμα ιστορικού πριν την ανθολόγηση.
Figure 80. Παράδειγμα ιστορικού πριν την ανθολόγηση.

Αν θέλουμε να έλξουμε την υποβολή e43a6 στον κύριο κλάδο μας, μπορούμε να εκτελέσουμε:

$ git cherry-pick e43a6fd3e94888d76779ad79fb568ed180e5fcdf
Finished one cherry-pick.
[master]: created a0a41a9: "More friendly message when locking the index fails."
 3 files changed, 17 insertions(+), 3 deletions(-)

Αυτή η εντολή έλκει την ίδια αλλαγή με αυτήν που εισήχθη στην e43a6, αλλά παίρνουμε μια νέα τιμή SHA-1 για την υποβολή επειδή η ημερομηνία κατά την οποία εφαρμόστηκε είναι διαφορετική. Τώρα η ιστορικό μας μοιάζει με αυτό:

Ιστορικό μετά την ανθολόγηση υποβολής σε έναν θεματικό κλάδο.
Figure 81. Ιστορικό μετά την ανθολόγηση υποβολής σε έναν θεματικό κλάδο.

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

Rerere

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

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

Αυτό το χαρακτηριστικό αποτελείται από δύο μέρη: μια ρύθμιση διαμόρφωσης και μια εντολή. Η ρύθμιση διαμόρφωσης είναι rerere.enabled και είναι αρκετά βολική ώστε να κερδίσει θέση στο καθολικό αρχείο config:

$ git config --global rerere.enabled true

Τώρα, κάθε φορά που κάνουμε μια συγχώνευση που επιλύει διενέξεις, η επίλυση θα καταγράφεται στην κρυφή μνήμη της rerere για την περίπτωση που τη χρειαστούμε στο μέλλον.

Αν χρειαστεί, μπορούμε να αλληλεπιδράσουμε με τη μνήμη cache rerere χρησιμοποιώντας την εντολή git rerere. Όταν καλείται χωρίς διακόπτες, το Git ελέγχει τη βάση δεδομένων επιλύσεων και προσπαθεί να βρει μια αντιστοίχιση με τις τρέχουσες συγκρούσεις συγχώνευσης και να τις επιλύσει (αν και αυτό γίνεται αυτόματα αν το rerere.enabled οριστεί σε true). Υπάρχουν επίσης δευτερεύουσες εντολές για να δούμε τι θα εγγραφεί, να διαγράψουμε συγκεκριμένη ανάλυση από την προσωρινή μνήμη και να καθαρίσουμε ολόκληρη τη μνήμη cache. Θα καλύψουμε την ανανέωση με περισσότερες λεπτομέρειες στην ενότητα Rerere.

Δημιουργία ετικετών στις εκδόσεις μας

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

$ git tag -s v1.5 -m 'my signed 1.5 tag'
You need a passphrase to unlock the secret key for
user: "Scott Chacon <schacon@gmail.com>"
1024-bit DSA key, ID F721C45A, created 2009-02-09

Εάν υπογράφουμε τις ετικέτες μας, μπορεί να έχουμε πρόβλημα όσον αφορά στη διανομή του δημόσιου κλειδιού PGP που χρησιμοποιείται για την υπογραφή των ετικετών μας. Ο διαχειριστής του έργου Git έχει λύσει αυτό το ζήτημα συμπεριλαμβάνοντας το δημόσιο κλειδί του ως ένα blob στο αποθετήριο και στη συνέχεια προσθέτοντας μια ετικέτα που δείχνει κατευθείαν σε αυτό το περιεχόμενο. Για να το κάνουμε αυτό, πρέπει να καταλάβουμε ποιο κλειδί θέλουμε και αυτό το κάνουμε τρέχοντας την gpg --list-keys:

$ gpg --list-keys
/Users/schacon/.gnupg/pubring.gpg
---------------------------------
pub   1024D/F721C45A 2009-02-09 [expires: 2010-02-09]
uid                  Scott Chacon <schacon@gmail.com>
sub   2048g/45D02282 2009-02-09 [expires: 2010-02-09]

Στη συνέχεια, μπορούμε να εισάγουμε απευθείας το κλειδί στη βάση δεδομένων Git, αν το εξάγουμε και το παροχετεύσουμε στο git hash-object, το οποίο γράφει ένα νέο blob με αυτά τα περιεχόμενα στο Git και μας επιστρέφει τον SHA-1 του blob:

$ gpg -a --export F721C45A | git hash-object -w --stdin
659ef797d181633c87ec71ac3f9ba29fe5775b92

Τώρα που έχουμε τα περιεχόμενα του κλειδιού μας στο Git, μπορούμε να δημιουργήσουμε μια ετικέτα που να δείχνει απευθείας σε αυτό δίνοντας τη νέα τιμή SHA-1 που μας έδωσε η εντολή hash-object:

$ git tag -a maintainer-pgp-pub 659ef797d181633c87ec71ac3f9ba29fe5775b92

Εάν εκτελέσουμε git push --tags, η ετικέτα maintainer-pgp-pub θα κοινοποιηθεί σε όλους. Αν κάποιος θέλει να επαληθεύσει μια ετικέτα, μπορεί να εισάγει απευθείας το κλειδί μας PGP έλκοντας το blob απευθείας από τη βάση δεδομένων και εισάγοντάς το στο GPG:

$ git show maintainer-pgp-pub | gpg --import

Μπορεί να χρησιμοποιήσει αυτό το κλειδί για να ελέγξει όλες τις ετικέτες που έχουμε υπογράψει. Επίσης, αν συμπεριλάβουμε οδηγίες στο μήνυμα ετικέτας, η λειτουργία git show <ετικέτα> θα μας επιτρέψει να δώσουμε στον τελικό χρήστη πιο συγκεκριμένες οδηγίες σχετικά με την επαλήθευση ετικετών.

Παραγωγή αριθμού έκδοσης

Επειδή το Git δεν έχει αύξοντες αριθμούς όπως v123 ή το ισοδύναμο για τις υποβολές, αν θέλουμε να έχουμε ένα ανθρωπανάγνωστο όνομα για κάθε υποβολή, μπορούμε να εκτελέσουμε git describe σε αυτήν την υποβολή. Το Git μάς δίνει το όνομα της πλησιέστερης ετικέτας με τον αριθμό υποβολών στην κορυφή της ετικέτας και τη μερική τιμή SHA-1 της υποβολής που περιγράφουμε:

$ git describe master
v1.6.2-rc1-20-g8c5b85c

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

Η εντολή git describe ευνοεί τις ετικέτες με επισημειώσεις (ετικέτες που δημιουργούνται με τις σημαίες -a ή -s), έτσι οι ετικέτες των εκδόσεων πρέπει να δημιουργούνται με αυτό τον τρόπο αν χρησιμοποιούμε την git describe, ώστε να διασφαλιστεί ότι η υποβολή αποκτά κατάλληλο όνομα κατά την περιγραφή της. Μπορούμε επίσης να χρησιμοποιήσουμε αυτήν τη συμβολοσειρά ως τον στόχο μιας εντολής checkout ή show αν και βασίζεται στη συντομευμένη τιμή SHA-1 (τελευταία ψηφία), οπότε ίσως να μην ισχύει για πάντα. Για παράδειγμα, ο πυρήνας Linux αυξήθηκε πρόσφατα από 8 σε 10 χαρακτήρες για να εξασφαλίσει τη μοναδικότητα αντικειμένων SHA-1, με αποτέλεσμα τα παλαιότερα ονόματα που δημιουργήθηκαν από την git describe να μην είναι έγκυρα πλέον.

Προετοιμασία μίας έκδοσης

Τώρα θέλουμε να δημοσιεύσουμε μία build. Ένα από τα πράγματα που θα θελήσουμε να κάνουμε είναι να δημιουργήσουμε ένα αρχείο του τελευταίου στιγμιότυπου του κώδικά μας για τις φτωχές ψυχές που δεν χρησιμοποιούν το Git. Η εντολή για να γίνει αυτό είναι git archive:

$ git archive master --prefix='project/' | gzip > `git describe master`.tar.gz
$ ls *.tar.gz
v1.6.2-rc1-20-g8c5b85c.tar.gz

Αν κάποιος ανοίξει αυτό το tarball, θα πάρει το τελευταίο στιγμιότυπο του έργου μας μέσα σε έναν κατάλογο. Μπορούμε επίσης να δημιουργήσουμε ένα αρχείο zip με τον ίδιο τρόπο, αν περάσουμε την επιλογή --format = zip στην git archive:

$ git archive master --prefix='project/' --format=zip > `git describe master`.zip

Τώρα έχουμε ένα ωραίο αρχείο tarball και ένα αρχείο zip της έκδοσης του έργου μας που μπορούμε να ανεβάσουμε στον ιστότοπό μας ή να στείλουμε με e-mail.

Η εντολή shortlog

Ήρθε η ώρα να στείλουμε e-mail στα μέλη της ηλεκτρονικής λίστας αλληλογραφίας που θέλουν να μάθουν τι συμβαίνει στο έργο μας. Ένας καλός τρόπος να αποκτήσουμε γρήγορα ένα είδος μητρώου αλλαγών (changelog) από αυτό που έχει προστεθεί στο έργο μας από την τελευταία έκδοση ή το e-mail μας είναι να χρησιμοποιήσουμε την εντολή git shortlog. Συνοψίζει όλες τις υποβολές στο εύρος υποβολών που της δίνουμε· για παράδειγμα, παρακάτω δίνεται μια περίληψη όλων των υποβολών από την τελευταία έκδοση, εάν η τελευταία έκδοση μας ονομάστηκε v1.0.1:

$ git shortlog --no-merges master --not v1.0.1
Chris Wanstrath (8):
      Add support for annotated tags to Grit::Tag
      Add packed-refs annotated tag support.
      Add Grit::Commit#to_patch
      Update version and History.txt
      Remove stray `puts`
      Make ls_tree ignore nils

Tom Preston-Werner (4):
      fix dates in history
      dynamic version method
      Version bump to 1.0.2
      Regenerated gemspec for version 1.0.2

Παίρνουμε μια καθαρή σύνοψη όλων των υποβολών από την v1.0.1 και μετά, ομαδοποιημένων κατά συγγραφέα, που μπορούμε να στείλουμε με e-mail στη λίστα μας.