-
1. Ξεκινώντας με το Git
-
2. Τα θεμελιώδη στοιχεία του Git
-
3. Διακλαδώσεις στο Git
-
4. Το Git στον διακομιστή
- 4.1 Τα πρωτόκολλα
- 4.2 Εγκατάσταση του Git σε διακομιστή
- 4.3 Δημιουργία δημόσιου κλειδιού SSH
- 4.4 Στήσιμο του διακομιστή
- 4.5 Δαίμονες του Git
- 4.6 Έξυπνο HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Επιλογές φιλοξενίας από τρίτους
- 4.10 Ανακεφαλαίωση
-
5. Κατανεμημένο Git
-
6. GitHub
-
7. Εργαλεία του Git
- 7.1 Επιλογή αναθεώρησης
- 7.2 Διαδραστική εργασία με το στάδιο καταχώρισης
- 7.3 Αποθέματα και Καθαρισμός
- 7.4 Υπογραφή της δουλειάς μας
- 7.5 Αναζήτηση
- 7.6 Η ιστορία ξαναγράφεται
- 7.7 Απομυθοποίηση της reset
- 7.8 Προχωρημένη Συγχώνευση
- 7.9 Rerere
- 7.10 Αποσφαλμάτωση με το Git
- 7.11 Υπομονάδες
- 7.12 Δεμάτιασμα δεδομένων
- 7.13 Replace
- 7.14 Αποθήκευση διαπιστευτηρίων
- 7.15 Ανακεφαλαίωση
-
8. Εξατομίκευση του Git
-
9. Το Git και άλλα συστήματα
- 9.1 Το Git ως πελάτης
- 9.2 Μετανάστευση στο Git
- 9.3 Ανακεφαλαίωση
-
10. Εσωτερική λειτουργία του Git
- 10.1 Διοχετεύσεις και πορσελάνες
- 10.2 Αντικείμενα του Git
- 10.3 Αναφορές του Git
- 10.4 Πακετάρισμα αρχείων
- 10.5 Τα refspec
- 10.6 Πρωτόκολλα μεταφοράς
- 10.7 Διατήρηση και ανάκτηση δεδομένων
- 10.8 Μεταβλητές περιβάλλοντος
- 10.9 Ανακεφαλαίωση
-
A1. Appendix A: Το Git σε άλλα περιβάλλοντα
- A1.1 Γραφικές διεπαφές
- A1.2 Το Git στο Visual Studio
- A1.3 Git στο Visual Studio Code
- A1.4 Git στο IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.5 Git στο Sublime Text
- A1.6 Το Git στο Bash
- A1.7 Το Git στο Zsh
- A1.8 Το Git στο Powershell
- A1.9 Ανακεφαλαίωση
-
A2. Appendix B: Ενσωμάτωση του Git στις εφαρμογές μας
- A2.1 Γραμμή εντολών Git
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Appendix C: Εντολές Git
- A3.1 Ρύθμιση και διαμόρφωση
- A3.2 Λήψη και δημιουργία έργων
- A3.3 Βασική λήψη στιγμιοτύπων
- A3.4 Διακλάδωση και συγχώνευση
- A3.5 Κοινή χρήση και ενημέρωση έργων
- A3.6 Επιθεώρηση και σύγκριση
- A3.7 Αποσφαλμάτωση
- A3.8 Επιθέματα
- A3.9 Ηλεκτρονικό ταχυδρομείο
- A3.10 Εξωτερικά Συστήματα
- A3.11 Διοίκηση
- A3.12 Εντολές διοχέτευσης
5.3 Κατανεμημένο Git - Συντήρηση ενός έργου
Συντήρηση ενός έργου
Εκτός από το να γνωρίζουμε πως να συμβάλλουμε αποτελεσματικά σε ένα έργο, πιθανότατα θα χρειαστεί να ξέρουμε πως να διαχειριζόμαστε ένα αποθετήριο.
Αυτό μπορεί να συνίσταται στην αποδοχή και εφαρμογή των επιθεμάτων που δημιουργούνται με το format-patch και στέλνονται με e-mail σε εμάς ή στην ενσωμάτωση αλλαγών σε απομακρυσμένους κλάδους για αποθετήρια που έχουμε προσθέσει ως απομακρυσμένα στο έργο μας.
Είτε διαχειριζόμαστε ένα τυπικό αποθετήριο είτε θέλουμε να βοηθήσουμε επαληθεύοντας ή εγκρίνοντας επιθέματα, πρέπει να ξέρουμε πως να δέχομαστε την εργασία με τρόπο που είναι κατά το δυνατό ξεκάθαρος στους άλλους συνεργάτες και βιώσιμος από εμάς μακροπρόθεσμα.
Εργασία σε θεματικούς κλάδους
Όταν σκεφτόμαστε να ενσωματώσουμε νέα δουλειά, είναι γενικά καλή ιδέα να το δοκιμάζουμε σε έναν θεματικό κλάδο — έναν προσωρινό κλάδο ειδικά σχεδιασμένο για να δοκιμάσουμε αυτή την εργασία.
Με αυτόν τον τρόπο, είναι εύκολο να τροποποιήσουμε ένα επίθεμα ξεχωριστά και να το παρατήσουμε αν δεν λειτουργεί μέχρι να έχουμε χρόνο να ξανασχολήθουμε μαζί του.
Αν δημιουργήσουμε ένα απλό όνομα κλάδου με βάση το θέμα της εργασίας που πρόκειται να δοκιμάσουμε, όπως ruby_client ή κάτι παρόμοια περιγραφικό, μπορούμε εύκολα να το θυμόμαστε, ακόμα κι αν χρειαστεί να το εγκαταλείψουμε για αρκετό καιρό και να επιστρέψουμε σε αυτό αργότερα.
Οι διαχειριστές έργων Git συνηθίζουν επίσης να οργανώνουν αυτούς τους κλάδους σε ονοματοχώρους (namespaces) — όπως sc/ruby_client, όπου το sc είναι συντομογραφία για τον προγραμματιστή που συνεισέφερε την εργασία.
Όπως θυμόμαστε, μπορούμε να δημιουργήσουμε τον κλάδο που να βασίζεται στο master κλάδο μας ως εξής:
$ git branch sc/ruby_client master
Εναλλακτικά, αν θέλουμε να μεταβούμε σε αυτόν αμέσως, μπορούμε να χρησιμοποιήσουμε την επιλογή -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-see-if-this-helps-the-gem.patch
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Εάν δεν τυπωθεί κάτι, τότε το επίθεμα θα πρέπει να εφαρμοστεί χωρίς προβλήματα. Αυτή η εντολή επίσης τερματίζει με μη-μηδενική κατάσταση αν ο έλεγχος αποτύχει, οπότε μπορούμε να τη χρησιμοποιήσουμε σε scripts, αν χρειαστεί.
Εφαρμογή επιθέματος με την am
Εάν ο συνεισφέρων είναι χρήστης του Git και ήταν αρκετά καλός ώστε να χρησιμοποιήσει την εντολή format-patch για να δημιουργήσει το επίθεμα, τότε η εργασία μας είναι ευκολότερη διότι το επίθεμα περιέχει πληροφορίες για τον συγγραφέα και ένα μήνυμα υποβολής για εμάς.
Αν είναι δυνατό, καλό είναι να ενθαρρύνουμε τους συνεργάτες μας να χρησιμοποιούν την format-patch αντί για την diff για να δημιουργούν επιθέματα για μας.
Θα πρέπει να χρησιμοποιούμε την git apply μόνον για επιθέματα παλιού τύπου (legacy).
Για να εφαρμόσουμε ένα επίθεμα που δημιουργείται με την format-patch, χρησιμοποιούμε την git am (η εντολή ονομάζεται am γιατί χρησιμοποιείται ως "εφαρμογή μιας σειράς επιθεμάτων από το mailbox").
Από τεχνικής άποψης, η git am έχει φτιαχτεί για να διαβάζει ένα αρχείο mbox, που είναι μία απλή μορφή αρχείου κειμένου για την αποθήκευση ενός ή περισσοτέρων μηνυμάτων e-mail σε ένα αρχείο κειμένου.
Μοιάζει με κάτι τέτοιο:
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
Αυτή είναι η αρχή της εξόδου της εντολής git format-patch που είδαμε στην προηγούμενη ενότητα· επίσης είναι μια έγκυρη μορφή ηλεκτρονικού μηνύματος mbox.
Εάν κάποιος μας έχει στείλει ηλεκτρονικά το επίθεμα χρησιμοποιώντας την git send-email και το κατεβάσουμε σε μορφή mbox, τότε μπορούμε να κατευθυνούμε την git am στο αρχείο mbox και αυτό θα αρχίσει να εφαρμόζει όλα τα επιθέματα που βλέπει.
Εάν τρέχουμε ένα πρόγραμμα-πελάτη e-mail που μπορεί να αποθηκεύσει πολλά μηνύματα e-mail σε μορφή mbox, μπορούμε να αποθηκεύσουμε ολόκληρες σειρές επιθεμάτων σε ένα αρχείο και στη συνέχεια να χρησιμοποιήσουμε την git am για να εφαρμόσουμε το καθένα ξεχωριστά.
Ωστόσο, αν κάποιος χρήστης ανέβασε ένα επίθεμα που δημιουργήθηκε με την git format-patch σε ένα σύστημα εισιτηρίων ή κάτι παρόμοιο, μπορούμε να αποθηκεύσουμε το αρχείο τοπικά και να περάσουμε το αρχείο που αποθηκεύσαμε στην git am για να το εφαρμόσουμε:
$ git am 0001-limit-log-function.patch
Applying: Add limit to log function
Μπορούμε να δούμε ότι το επίθεμα εφαρμόστηκε καθαρά και η νέα υποβολή δημιουργήθηκε για μας αυτόματα.
Οι πληροφορίες του συγγραφέα λαμβάνονται από τις κεφαλίδες From και Date του e-mail και το μήνυμα της υποβολής λαμβάνεται από το 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-see-if-this-helps-the-gem.patch
Applying: See 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: See if this helps the gem
Εάν θέλουμε το Git να δοκιμάσει να επιλύσει τη σύγκρουση με λίγο πιο έξυπνο τρόπο, μπορούμε να περάσουμε την επιλογή -3 στην git am, η οποία θα κάνει το Git να επιχειρήσει μια τριμερή συγχώνευση.
Αυτή η επιλογή δεν είναι ενεργοποιημένη εξ ορισμού, επειδή δεν λειτουργεί εφόσον η υποβολή στην οποία λέει το επίθεμα ότι βασίζεται δεν βρίσκεται στο αποθετήριό μας.
Αν έχουμε αυτή την υποβολή — για παράδειγμα αν το επίθεμα βασίστηκε σε δημόσια υποβολή — τότε η επιλογή -3 είναι γενικά πολύ πιο έξυπνη όσον αφορά στην εφαρμογή ενός συγκρουόμενου επιθέματος λογισμικού:
$ git am -3 0001-see-if-this-helps-the-gem.patch
Applying: See 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, το επίθεμα θα θεωρούνταν σύγκρουση.
Αφού χρησιμοποιήθηκε η επιλογή -3, το επίθεμα εφαρμόστηκε καθαρά.
Αν εφαρμόζουμε μια σειρά από επιθέματα από ένα mbox, μπορούμε επίσης να εκτελέσουμε την εντολή am σε διαδραστική (interactive) λειτουργία, η οποία σταματά σε κάθε επίθεμα που βρίσκει και ρωτά αν θέλουμε να το εφαρμόσουμε:
$ git am -3 -i mbox
Commit Body is:
--------------------------
See if this helps the gem
--------------------------
Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all
Αυτό είναι βολικό όταν έχουμε αποθηκεύσει μερικά επιθέματα, επειδή μπορούμε να τα δούμε πρώτα ή να μην τα εφαρμόσουμε εάν το έχουμε κάνει ήδη.
Όταν όλα τα επιθέματα για το θέμα μας εφαρμοστούν και υποβληθούν στον κλάδο μας, μπορούμε να επιλέξουμε εάν και πως θα τα ενσωματώσουμε σε έναν μακροβιότερο κλάδο.
Checkοut απομακρυσμένων κλάδων
Εάν η συνεισφορά προήλθε από έναν χρήστη Git που δημιούργησε το δικό του αποθετήριο, ώθησε μερικές αλλαγές σε αυτό και έπειτα μας έστειλε τη διεύθυνση URL του αποθετηρίου και το όνομα του απομακρυσμένου κλάδου στον οποίο έγιναν οι αλλαγές, μπορούμε να το προσθέσουμε ως απομακρυσμένο και να κάνουμε συγχωνεύσεις τοπικά.
Για παράδειγμα, εάν η Τζέσικα μας στείλει ένα e-mail που λέει ότι έχει μία εξαιρετική νέα λειτουργικότητα στον κλάδο ruby-client του αποθετηρίου της, μπορούμε να τη δοκιμάσουμε προσθέτοντας το αποθετήριο ως απομακρυσμένο και κάνοντας check out τον κλάδο τοπικά:
$ git remote add jessica https://github.com/jessica/myproject.git
$ git fetch jessica
$ git checkout -b rubyclient jessica/ruby-client
Εάν η Τζέσικα ξαναστείλει e-mail αργότερα με κάποιον άλλο κλάδο που περιέχει άλλο ένα εξαιρετικό χαρακτηριστικό, μπορούμε να κάνουμε απευθείας fetch και checkout τοπικά διότι έχουμε ήδη το απομακρυσμένο αποθετήριο εγκατεστημένο.
Αυτό είναι ιδιαίτερα χρήσιμο αν συνεργαζόμαστε με ένα άτομο συχνά. Εάν κάποιος συνεισφέρει ένα επίθεμα μόνο μία στις τόσες, τότε η αποδοχή του μέσω e-mail είναι ενδεχομένως λιγότερο χρονοβόρα από το να πρέπει όλοι να τρέχουν το δικό τους διακομιστή και να προσθαφαιρούν απομακρυσμένους διακομιστές συνεχώς για να πάρουν μιά χούφτα επιθέματα. Επίσης, είναι μάλλον απίθανο να θέλουμε να έχουμε εκατοντάδες απομακρυσμένους διακομιστές, έναν για καθένα που συνεισφέρει καναδυό επιθέματα μια στο τόσο. Ωστόσο, τα scripts και φιλοξενούμενες υπηρεσίες μπορεί να μας διευκολύνουν — αυτό εξαρτάται σε μεγάλο βαθμό από τον τρόπο με τον οποίο εμείς και οι συνεισφέροντες αναπτύσσουμε τον κώδικά μας.
Το άλλο πλεονέκτημα αυτής της προσέγγισης είναι ότι έχουμε και το ιστορικό των υποβολών.
Παρόλο που ίσως έχουμε ζητήματα συγχώνευσης, γνωρίζουμε πού βρίσκεται η σχετική εργασία τους στο ιστορικό μας· μια κατάλληλη τριμερής συγχώνευση είναι η προτιμότερη επιλογή από τη χρήση της επιλογής -3 με την ελπίδα ότι το επίθεμα δημιουργήθηκε από μια δημόσια υποβολή στην οποία έχουμε πρόσβαση.
Εάν δεν συνεργαζόμαστε συχνά με ένα άτομο, αλλά εξακολουθούμε να θέλουμε να ελκύσουμε (pull) από αυτόν με αυτό τον τρόπο, μπορούμε να δώσουμε τη διεύθυνση URL του απομακρυσμένου αποθετηρίου στην εντολή git pull.
Αυτό κάνει ένα και μοναδικό ελκυσμό και δεν αποθηκεύει τη διεύθυνση URL ως απομακρυσμένη αναφορά:
$ git pull https://github.com/onetimeguy/project
From https://github.com/onetimeguy/project
* branch HEAD -> FETCH_HEAD
Merge made by the 'recursive' strategy.
Προσδιορισμός του τι έχει εισαχθεί
Τώρα έχουμε έναν θεματικό κλάδο που περιέχει συνεισφορές. Σε αυτό το σημείο, μπορούμε να αποφασίσουμε τι θα θέλαμε να κάνουμε με αυτόν. Αυτή η ενότητα επανεξετάζει μερικές εντολές, ώστε να μπορούμε να δούμε πως μπορούμε να τις χρησιμοποιήσουμε για να ελέγξουμε τι ακριβώς θα εισάγουμε αν συγχωνεύσουμε αυτόν τον θεματικό κλάδο στον κύριο κλάδο μας.
Συχνά είναι χρήσιμο να πάρουμε μια ανασκόπηση όλων των υποβολών που βρίσκονται σε αυτόν τον κλάδο, αλλά δεν βρίσκονται στον master κλάδο μας.
Μπορούμε να αποκλείσουμε υποβολές στον master μας προσθέτοντας την επιλογή --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
See if this helps the gem
commit 7482e0d16d04bea79d0dba8988cc78df655f16a0
Author: Scott Chacon <schacon@gmail.com>
Date: Mon Oct 22 19:38:36 2008 -0700
Update gemspec to hopefully work better
Για να δούμε τι αλλαγές εισάγει κάθε υποβολή, θυμόμαστε ότι μπορούμε να περάσουμε την επιλογή -p στο git log και θα προσαρτήσει το αποτέλεσμα της diff που εισήχθη σε κάθε υποβολή.
Για να δούμε το πλήρες diff που θα παίρναμε αν συγχωνεύαμε αυτόν τον θεματικό κλάδο με ένα άλλο κλάδο, ίσως χρειαστεί να χρησιμοποιήσουμε ένα περίεργο τέχνασμα για να έχουμε τα σωστά αποτελέσματα. Ίσως σκεφτούμε να εκτελέσουμε το εξής:
$ git diff master
Αυτή η εντολή μας δίνει ένα diff, αλλά μπορεί να είναι παραπλανητικό.
Εάν ο κλάδος μας master έχει προχωρήσει από τότε που δημιουργήσαμε αυτόν τον θεματικό κλάδο, τότε θα πάρουμε φαινομενικά παράξενα αποτελέσματα.
Αυτό συμβαίνει επειδή το Git συγκρίνει άμεσα το στιγμιότυπο της τελευταίας υποβολής του θεματικού κλάδου στον οποίο βρισκόμαστε με το στιγμιότυπο της τελευταίας υποβολής στον κλάδο master.
Για παράδειγμα, αν έχουμε προσθέσει μια γραμμή σε ένα αρχείο στον κλάδο master, μια άμεση σύγκριση των στιγμιότυπων θα μοιάζει σαν ο θεματικός κλάδος να πρόκειται να καταργήσει αυτή τη γραμμή.
Αν ο κλάδος master είναι άμεσος πρόγονος του θεματικού κλάδου μας, αυτό δεν είναι πρόβλημα· αλλά αν τα δύο ιστορικά έχουν αποκλίσει, η διαφορά θα μοιάζει σαν να προσθέτουμε όλα τα νέα στοιχεία στον θεματικό κλάδο και να καταργούμε ό,τι υπάρχει μόνον στον κλάδο master.
Αυτό που πραγματικά θέλουμε να δούμε είναι οι αλλαγές που έχουν προστεθεί στον θεματικό κλάδο — την εργασία που θα εισάγουμε αν συγχωνεύσουμε αυτόν τον κλάδο με τον master κλάδο.
Αυτό μπορούμε να το κάνουμε βάζοντας το Git να συγκρίνει την τελευταία υποβολή στον θεματικό κλάδο μας με τον πρώτο κοινό πρόγονο που έχει με τον master κλάδο.
Από τεχνικής άποψης, αυτό μπορούμε να το καταφέρουμε αν εντοπίσουμε τον κοινό πρόγονο και στη συνέχεια τρέξουμε τη διαφορά diff σε αυτό:
$ git merge-base contrib master
36c7dba2c95e6bbb78dfa822519ecfec6e1ca649
$ git diff 36c7db
ή πιο συνεκτικά:
$ git diff $(git merge-base contrib master)
Ωστόσο, κανένα από τα δύο δεν είναι ιδιαίτερα βολικό, γι' αυτό το Git παρέχει μια άλλη συντόμευση για να κάνει το ίδιο πράγμα: τη σύνταξη με τις τρεις τελείες.
Στο πλαίσιο της εντολής git diff, μπορούμε να βάλουμε τρεις τελείες μετά από ένα άλλο κλάδο για να κάνουμε ένα diff μεταξύ της τελευταίας υποβολής του κλάδου που βρισκόμαστε και του κοινού προγόνου της με έναν άλλο κλάδο:
$ git diff master...contrib
Αυτή η εντολή μας δείχνει μόνο τη δουλειά που έχει εισάγει ο τρέχων θεματικός κλάδος μας από τον κοινό πρόγονο του με τον master.
Αυτή είναι μια πολύ χρήσιμη σύνταξη που πρέπει να θυμόμαστε.
Ενσωμάτωση συνεισφερθείσας εργασίας
Όταν όλη δουλειά στον θεματικό κλάδο μας είναι έτοιμη να ενσωματωθεί σε ένα πιο κεντρικό κλάδο, το ερώτημα που ανακύπτει είναι πως να το κάνουμε. Επιπλέον, ποια ροή εργασίας θέλουμε να χρησιμοποιήσουμε για να συντηρήσουμε το έργο μας; Έχουμε αρκετές επιλογές και στη συνέχεια θα καλύψουμε μερικές από αυτές.
Συγχώνευση ροών εργασίας
Μια απλή ροή εργασίας είναι αυτή στην οποία συγχωνεύουμε την εργασία μας στον κλάδο master.
Σε αυτό το σενάριο, έχουμε ένα κύριο κλάδο που περιέχει κυρίως ευσταθή κώδικα.
Όταν εργαζόμαστε σε έναν θεματικό κλάδο που έχουμε φτιάξει ή τον οποίο έχει συνεισφέρει κάποιος και έχουμε επαληθεύσει, τον συγχωνεύουμε στον master μας, διαγράφουμε τον θεματικό κλάδο και στη συνέχεια συνεχίζουμε τη διαδικασία.
Εάν έχουμε ένα αποθετήριο με εργασία σε δύο κλάδους που ονομάζονται ruby_client και php_client, που μοιάζει με το Ιστορικό με θεματικούς κλάδους, και συγχωνεύσουμε πρώτα τον ruby_client και στη συνέχεια τον php_client, τότε το ιστορικό μας θα καταλήξει να μοιάζει με το Ιστορικό μετά από συγχώνευση θεματικών κλάδων.
Αυτή είναι πιθανότατα η απλούστερη ροή εργασίας, αλλά μπορεί να είναι προβληματική αν έχουμε να κάνουμε με μεγαλύτερα ή πιο ευσταθή έργα στα οποία θέλουμε να είμαστε πολύ προσεκτικοί σχετικά με το τι εισάγουμε.
Αν έχουμε ένα πιο σημαντικό έργο, ίσως θέλουμε να χρησιμοποιήσουμε έναν κύκλο συγχώνευσης δύο φάσεων.
Σε αυτό το σενάριο έχουμε δύο μακρόβιους κλάδους, master και develop, στους οποίους καθορίζουμε ότι ο master ενημερώνεται μόνο όταν δημιουργείται μία πολύ σταθερή έκδοση και όλος ο νέος κώδικας είναι ενσωματωμένος στον κλάδο develop.
Ωθούμε και τους δύο αυτούς κλάδους τακτικά σε ένα δημόσιο αποθετήριο.
Κάθε φορά που έχουμε έναν νέο θεματικό κλάδο να συγχωνεύσουμε (Πριν από τη συγχώνευση θεματικού κλάδου.), τον συγχωνεύουμε στον κλάδο develop (Μετά τη συγχώνευση θεματικού κλάδου.)· τότε, όταν κάνουμε tag μια έκδοση (release), ταχυπροωθούμε τον master σε όποιον κλάδο βρίσκεται ο ευσταθής κλάδος develop (Μετά τη δημοσιοποίηση έκδοσης.).
Με αυτόν τον τρόπο, όταν κάποιος κλωνοποιεί το αποθετήριο του έργου μας, μπορεί είτε να μεταβεί στον κλάδο master, να χτίσει την πιο πρόσφατη ευσταθή έκδοση και να συμβαδίζει με αυτή εύκολα, είτε να μεταβεί στον κλάδο develop, που περιέχει τις πιο πρόσφατες εξελίξεις.
Μπορούμε επίσης να επεκτείνουμε αυτό το μοντέλο και να έχουμε έναν κλάδο ολοκλήρωσης integrate στο οποίο όλες οι εργασίες συγχωνεύονται.
Στη συνέχεια, όταν το codebase σε αυτόν τον κλάδο είναι ευσταθές και περνάει τα τεστ, μπορούμε να το συγχωνεύσουμε σε έναν κλάδο develop· και όταν και αυτός έχει αποδειχθεί ευσταθής για κάποιο χρονικό διάστημα, τον ταχυπροωθούμε στον master κλάδο μας.
Ροές εργασίας μεγάλης συγχώνευσης
Το έργο Git έχει τέσσερις μακρόβιους κλάδους: τους master,` next` και seen (παλιότερα pu — proposed updates) για νέες εργασίες και τον maint για συντήρηση backport.
Όταν εισάγονται νέες εργασίες από συνεργάτες, συλλέγονται σε θεματικούς κλάδους στο αποθετήριο του διαχειριστή με τρόπο παρόμοιο με αυτόν που έχουμε περιγράψει (βλ. Διαχείριση περίπλοκης ακολουθίας παράλληλων συνεισφερθέντων θεματικών κλάδων).
Σε αυτό το σημείο, τα θέματα αξιολογούνται για να διαπιστωθεί αν είναι ασφαλή και έτοιμα προς κατανάλωση ή αν χρειάζονται περισσότερη δουλειά.
Αν είναι ασφαλή, συγχωνεύονται στον κλάδο next και αυτός ο κλάδος ωθείται, ώστε όλοι να μπορούν να δοκιμάσουν τα θέματα που ενσωματώθηκαν.
Εάν τα θέματα θέλουν ακόμα δουλειά, συγχωνεύονται στον seen.
Όταν διαπιστωθεί ότι είναι τελείως ευσταθή, τα θέματα επανασυγχωνεύονται στον master.
Στη συνέχεια οι κλάδοι next και seen γίνονται ξανά build από τον κλάδο master.
Αυτό σημαίνει ότι ο master προχωρά σχεδόν πάντα, ο next επανατοποθετείται περιστασιακά και ο seen επανατοποθετείται ακόμα πιο συχνά:
Όταν ένας θεματικός κλάδος έχει τελικά συγχωνευτεί στον master, αφαιρείται από το αποθετήριο.
Το έργο Git διαθέτει επίσης έναν κλάδο maint που αποσχίζεται (forked) από την τελευταία δημοσιευμένη έκδοση (release) ώστε να παρέχει επιθέματα backport για την περίπτωση που απαιτείται έκδοση συντήρησης.
Έτσι, όταν κλωνοποιούμε το αποθετήριο Git, έχουμε τέσσερις κλάδους στους οποίους μπορούμε να μεταβούμε για να αξιολογήσουμε το έργο σε διαφορετικά στάδια ανάπτυξης, ανάλογα με το πόσο σύγχρονος θέλουμε να είμαστε ή πως θέλουμε να συνεισφέρουμε· και ο συντηρητής έχει μια δομημένη ροή εργασίας για να τους βοηθήσει να ελέγξουν νέες συνεισφορές.
Η ροή εργασίας του Git είναι εξειδικευμένη.
Για να το κατανοήσουμε πλήρως μπορούμε να δούμε το Git Maintainer’s guide.
Ροές εργασίας με αλλαγή βάσης και ανθολόγηση
Άλλοι συντηρητές προτιμούν να επανατοποθετούν (rebase) ή να ανθολογούν (cherry-pick) τις συνεισφορές στην κορυφή του master κλάδου τους, αντί να τις συγχωνεύουν, για να διατηρήσουν ένα κυρίως γραμμικό ιστορικό.
Όταν εργαζόμαστε σε έναν θεματικό κλάδο και έχουμε αποφασίσει ότι θέλουμε να τον ενσωματώσουμε, μεταβαίνουμε σε αυτόν και εκτελούμε την εντολή rebase για να ξαναχτίσουμε τις αλλαγές με νέα βάση τον master (ή τον develop κ.ο.κ.) κλάδο μας.
Αν αυτό λειτουργεί καλά, τότε μπορούμε να ταχυπροωθήσουμε τον κύριο κλάδο μας οπότε θα καταλήξουμε με ένα γραμμικό ιστορικό έργου.
Ο άλλος τρόπος για να μετακινήσουμε εργασία που εισάγεται από τον ένα κλάδο στον άλλο είναι η ανθολόγηση (cherry-pick). Η ανθολόγηση στο Git είναι σαν μια αλλαγή βάσης μίας μόνο υποβολής. Παίρνει το επίθεμα που εισήχθη σε μια υποβολή και προσπαθεί να το ξαναεφαρμόσει στον κλάδο στον οποίο βρισκόμαστε αυτή τη στιγμή. Η ανθολόγηση είναι χρήσιμη εάν έχουμε αρκετές υποβολές σε έναν θεματικό κλάδο και θέλουμε να ενσωματώσουμε μόνο μία από αυτές ή εάν έχουμε μόνο μία υποβολή σε έναν θεματικό κλάδο και προτιμάμε να την ανθολογήσουμε αντί να αλλάξουμε τη βάση της. Για παράδειγμα, ας υποθέσουμε ότι έχουμε ένα έργο που μοιάζει με αυτό:
Αν θέλουμε να ελκύσουμε την υποβολή e43a6 στον master, μπορούμε να εκτελέσουμε:
$ git cherry-pick e43a6
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 για την υποβολή επειδή η ημερομηνία κατά την οποία εφαρμόστηκε είναι διαφορετική.
Τώρα το ιστορικό μας μοιάζει με αυτό:
Πλέον μπορούμε να καταργήσουμε τον θεματικό κλάδο και να εγκαταλείψουμε τις υποβολές που δεν θέλαμε να ελκύσουμε.
Rerere
Εάν κάνουμε πολλές συγχωνεύσεις και αλλαγές βάσης ή διατηρούμε ένα μακρόβιο θεματικό κλάδο, το Git διαθέτει μια λειτουργία που λέγεται “rerere” και η οποία μπορεί να μας βοηθήσει.
Rerere σημαίνει “reuse recorded resolution” (`"επαναχρησιμοποίηση καταγεγραμμένης επίλυσης`") — είναι ένας τρόπος σύντομης αντιμετώπισης της μη-αυτόματης επίλυσης συγκρούσεων. Όταν η rerere είναι ενεργοποιημένη, το Git θα διατηρήσει ένα σύνολο εικόνων πριν και μετά από επιτυχείς συγχωνεύσεις και αν παρατηρήσει ότι υπάρχει μια σύγκρουση που μοιάζει ακριβώς με μία που έχουμε ήδη επιλύσει, θα χρησιμοποιήσει απλώς το επίθεμα από την τελευταία φορά, χωρίς να μας ενοχλήσει.
Αυτό το χαρακτηριστικό αποτελείται από δύο μέρη: μια παραμετροποίηση και μια εντολή.
Η παραμετροποίηση είναι rerere.enabled και είναι αρκετά βολική ώστε να την έχουμε στο καθολικό μας αρχείο config:
$ git config --global rerere.enabled true
Τώρα, κάθε φορά που κάνουμε μια συγχώνευση που επιλύει διενέξεις, η επίλυση θα καταγράφεται στην κρυφή μνήμη για την περίπτωση που τη χρειαστούμε στο μέλλον.
Αν χρειαστεί, μπορούμε να αλληλεπιδράσουμε με τη μνήμη cache rerere χρησιμοποιώντας την εντολή git rerere.
Όταν καλείται χωρίς διακόπτες, το Git ελέγχει τη βάση δεδομένων επιλύσεων και προσπαθεί να βρει μια αντιστοίχιση με τις τρέχουσες συγκρούσεις συγχώνευσης και να τις επιλύσει (αν και αυτό γίνεται αυτόματα αν το rerere.enabled οριστεί σε true).
Υπάρχουν επίσης δευτερεύουσες εντολές για να δούμε τι θα εγγραφεί, να διαγράψουμε συγκεκριμένη ανάλυση από την προσωρινή μνήμη και να καθαρίσουμε ολόκληρη την προσωρινή μνήμη (cache).
Θα καλύψουμε την rerere με περισσότερες λεπτομέρειες στην ενότητα 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 <tag> θα μας επιτρέψει να δώσουμε στον τελικό χρήστη πιο συγκεκριμένες οδηγίες σχετικά με την επαλήθευση ετικετών.
Παραγωγή αριθμού build
Επειδή το Git δεν έχει αύξοντες αριθμούς όπως v123 ή το ισοδύναμο για τις υποβολές, αν θέλουμε να έχουμε ένα ανθρωπανάγνωστο όνομα για κάθε υποβολή, μπορούμε να εκτελέσουμε git describe σε αυτή την υποβολή.
Το Git κατασκευάζει ένα αλφαριθμητικό το οποίο αποτελείται από το όνομα της πλησιέστερης χρονικά ετικέτας με τον αριθμό υποβολών στην κορυφή της ετικέτας και τη μερική τιμή SHA-1 της υποβολής που περιγράφουμε (με τον χαρακτήρα “g” στην αρχή, που σημαίνει Git):
$ git describe master
v1.6.2-rc1-20-g8c5b85c
Με αυτό τον τρόπο, μπορούμε να εξάγουμε ένα στιγμιότυπο ή build και να τα ονομάσουμε με κάτι κατανοητό από ανθρώπους.
Μάλιστα, αν δημιουργήσουμε το Git από τον πηγαίο κώδικά του, που έχει κλωνοποιηθεί από το αποθετήριο Git, το git --version μας δίνει κάτι που μοιάζει με αυτό.
Αν περιγράφουμε μια υποβολή, στην οποία έχουμε προσαρτήσει μια ετικέτα, μας δίνει το όνομα της ετικέτας.
Η εντολή git describe απαιτεί επισημασμένες ετικέτες (ετικέτες που δημιουργούνται με τις σημαίες -a ή -s)· αν θέλουμε να χρησιμοποιήσουμε και τις απλές (μη-επισημασμένες) ετικέτες, προσθέτουμε την επιλογή --tags στην εντολή.
Μπορούμε επίσης να χρησιμοποιήσουμε αυτή τη συμβολοσειρά ως τον στόχο μιας εντολής git checkout ή git show αν και βασίζεται στη συντομευμένη τιμή SHA-1 (τα τελευταία ψηφία), οπότε ίσως να μην ισχύει για πάντα.
Για παράδειγμα, ο πυρήνας Linux αυξήθηκε πρόσφατα από 8 σε 10 χαρακτήρες για να εξασφαλίσει τη μοναδικότητα αντικειμένων SHA-1, με αποτέλεσμα τα παλαιότερα ονόματα που δημιουργήθηκαν από την git describe να μην είναι έγκυρα πλέον.
Προετοιμασία μίας δημοσιευμένης έκδοσης
Τώρα θέλουμε να δημοσιεύσουμε ένα build.
Ένα από τα πράγματα που θα θελήσουμε να κάνουμε είναι να δημιουργήσουμε ένα αρχείο (archive) του τελευταίου στιγμιότυπου του κώδικά μας για τα άτομα που δεν χρησιμοποιούν το 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, θα πάρει το τελευταίο στιγμιότυπο του έργου μας μέσα σε έναν κατάλογο με όνομα project.
Μπορούμε επίσης να δημιουργήσουμε ένα αρχείο zip με τον ίδιο τρόπο, αν περάσουμε την επιλογή --format=zip στην git archive:
$ git archive master --prefix='project/' --format=zip > `git describe master`.zip
Τώρα έχουμε ένα ωραιότατο tarball και ένα αρχείο zip της έκδοσης του έργου μας που μπορούμε να ανεβάσουμε στον ιστότοπό μας ή να στείλουμε με e-mail σε άλλους.
Η εντολή shortlog
Ήρθε η ώρα να στείλουμε e-mail στα μέλη της mailing list που θέλουν να μάθουν τι συμβαίνει στο έργο μας.
Ένας καλός τρόπος να αποκτήσουμε γρήγορα ένα είδος μητρώου αλλαγών (changelog) από ό,τι έχει προστεθεί στο έργο μας από την τελευταία έκδοση ή το e-mail μας είναι να χρησιμοποιήσουμε την εντολή git shortlog.
Συνοψίζει όλες τις υποβολές στο εύρος υποβολών που της δίνουμε· για παράδειγμα, παρακάτω δίνεται μια περίληψη όλων των υποβολών από την τελευταία έκδοση, εάν η τελευταία έκδοσή μας ονομάστηκε v1.0.1:
$ git shortlog --no-merges master --not v1.0.1
Chris Wanstrath (6):
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 στη λίστα μας.