Git
Chapters ▾ 2nd Edition

10.6 Εσωτερική λειτουργία του Git - Πρωτόκολλα μεταφοράς

Πρωτόκολλα μεταφοράς

Το Git μπορεί να μεταφέρει δεδομένα μεταξύ δύο αποθετηρίων με δύο βασικούς τρόπους: το χαζό (“dumb”) πρωτόκολλο και το έξυπνο (“smart”) πρωτόκολλο. Αυτή η ενότητα θα καλύψει γρήγορα τον τρόπο λειτουργίας αυτών των δύο βασικών πρωτοκόλλων.

Το χαζό πρωτόκολλο

Εάν ρυθμίζουμε ένα αποθετήριο που θα εμφανίζεται μόνο για ανάγνωση μέσω HTTP, το χαζό πρωτόκολλο είναι πιθανότατα αυτό που θα χρησιμοποιηθεί. Αυτό το πρωτόκολλο ονομάζεται “χαζό” επειδή δεν απαιτεί ειδικό κώδικα Git στην πλευρά του διακομιστή κατά τη διάρκεια της διαδικασίας μεταφοράς· η διαδικασία ανάκτησης είναι μια σειρά αιτημάτων GET HTTP, όπου ο πελάτης μπορεί να αναλάβει τη διάταξη του αποθετηρίου Git στον διακομιστή.

Note

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

Ας ακολουθήσουμε τη διαδικασία http-fetch για τη βιβλιοθήκη simplegit:

$ git clone http://server/simplegit-progit.git

Το πρώτο πράγμα που κάνει αυτή η εντολή είναι να κατεβάσει το αρχείο info/refs. Αυτό το αρχείο γράφεται με την εντολή update-server-info, γι' αυτό πρέπει να το ενεργοποιήσουμε ως άγκιστρο post-receive για να λειτουργήσει σωστά η μεταφορά HTTP:

=> GET info/refs
ca82a6dff817ec66f44342007202690a93763949     refs/heads/master

Τώρα έχουμε μια λίστα με τις απομακρυσμένες αναφορές και τους αριθμούς SHA-1. Στη συνέχεια, αναζητάμε ποια είναι η αναφορά του HEAD, ώστε να ξέρουμε τι να ελέγξουμε όταν τελειώσουμε:

=> GET HEAD
ref: refs/heads/master

Θα πρέπει να μεταβούμε στον κλάδο master, όταν ολοκληρώσουμε τη διαδικασία. Σε αυτό το σημείο, είμαστε έτοιμοι να ξεκινήσουμε τη διαδικασία. Επειδή το σημείο εκκίνησής μας είναι το αντικείμενο υποβολής ca82a6 που είδαμε στο αρχείο info/refs, ξεκινάμε με την ανάκτησή του:

=> GET objects/ca/82a6dff817ec66f44342007202690a93763949
(179 bytes of binary data)

Μας επιστρέφεται ένα αντικείμενο —το αντικείμενο βρίσκεται σε χαλαρή μορφή στον διακομιστή και το έχουμε ανακτήσει με ένα στατικό αίτημα GET HTTP. Μπορούμε να το αποσυμπιέσουμε με το zlib, να αφαιρέσουμε την κεφαλίδα και να εξετάσουμε το περιεχόμενο της υποβολής:

$ git cat-file -p ca82a6dff817ec66f44342007202690a93763949
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
author Scott Chacon <schacon@gmail.com> 1205815931 -0700
committer Scott Chacon <schacon@gmail.com> 1240030591 -0700

changed the version number

Έπειτα, έχουμε δύο επιπλέον αντικείμενα να ανακτήσουμε —το cfda3b, το δέντρο του περιεχομένου του οποίου την υποβολή μόλις ανακτήσαμε· και το 085bb3, που είναι η μητρική υποβολή:

=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
(179 bytes of data)

Αυτό μας δίνει το επόμενο αντικείμενο υποβολής μας. Παίρνουμε το αντικείμενο δέντρου:

=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf
(404 - Not Found)

Ωχ! Φαίνεται ότι το αντικείμενο δέντρου δεν είναι σε χαλαρή μορφή στον διακομιστή, οπότε παίρνουμε πίσω μια απάντηση 404. Υπάρχουν δύο λόγοι γι' αυτό —το αντικείμενο μπορεί να βρίσκεται σε ένα εναλλακτικό αποθετήριο ή μπορεί να είναι σε ένα πακέτο σε αυτό το αποθετήριο. Το Git ελέγχει πρώτα για καταχωρημένες εναλλακτικές:

=> GET objects/info/http-alternates
(empty file)

Αν αυτό μας επιστρέψει με μια λίστα εναλλακτικών διευθύνσεων URL, το Git ελέγχει τα χαλαρά αρχεία και πακέτα εκεί —αυτός είναι ένας ωραίος μηχανισμός για έργα που είναι διχάλες το ένα του άλλου για να μοιράζονται αντικείμενα στον δίσκο. Ωστόσο, επειδή σε αυτήν την περίπτωση δεν επιστρέφονται εναλλακτικές, το αντικείμενο πρέπει να είναι ένα πακέτο. Για να δούμε τι πακέτα είναι διαθέσιμα σε αυτόν τον διακομιστή, πρέπει να πάρουμε το αρχείο objects/info/packs, το οποίο περιέχει μια λίστα των πακέτων (που επίσης δημιουργείται από την update-server-info):

=> GET objects/info/packs
P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack

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

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx
(4k of binary data)

Τώρα που έχουμε το ευρετήριο πακέτων, μπορούμε να δούμε αν το αντικείμενο είναι μέσα σε αυτό —επειδή το ευρετήριο παραθέτει τους αριθμούς SHA-1s των αντικειμένων που περιέχονται στο πακέτο και τη μετατόπιση (offset) σε αυτά τα αντικείμενα. Το αντικείμενο μας είναι εκεί, συνεπώς προχωρούμε και παίρνουμε ολόκληρο το πακέτο:

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k of binary data)

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

Το έξυπνο πρωτόκολλο

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

Αποστολή δεδομένων

Για να μεταφορτώσει δεδομένα σε μια απομακρυσμένη τοποθεσία, το Git χρησιμοποιεί τις διαδικασίες send-pack και receive-pack. Η διαδικασία send-pack εκτελείται στον πελάτη και συνδέεται με μια διαδικασία receive-pack στην απομακρυσμένη πλευρά.

SSH

Για παράδειγμα, ας πούμε ότι τρέχουμε την git push master master στο έργο μας, και ως origin ορίζεται μια διεύθυνση URL που χρησιμοποιεί το πρωτόκολλο SSH. Το Git ενεργοποιεί τη διαδικασία send-pack, η οποία ενεργοποιεί μια σύνδεση μέσω SSH στον διακομιστή μας. Προσπαθεί να εκτελέσει μια εντολή στον απομακρυσμένο διακομιστή μέσω μιας κλήσης SSH που μοιάζει με κάτι τέτοιο:

$ ssh -x git@server "git-receive-pack 'simplegit-progit.git'"
00a5ca82a6dff817ec66f4437202690a93763949 refs/heads/master report-status \
	delete-refs side-band-64k quiet ofs-delta \
	agent=git/2:2.1.1+github-607-gfba4028 delete-refs
0000

Η εντολή git-receive-pack αποκρίνεται αμέσως με μία γραμμή για κάθε αναφορά που έχει αυτήν τη στιγμή —την περίπτωση αυτή, μόνο τον κλάδο master και τον SHA-1 του. Η πρώτη γραμμή περιέχει επίσης μια λίστα με τις δυνατότητες του διακομιστή (εδώ, status-report, delete-refs και μερικές άλλες, συμπεριλαμβανομένου του αναγνωριστικού του πελάτη).

Κάθε γραμμή ξεκινάει με μια τετραψήφια δεκαξαδική τιμή που καθορίζει πόσο μεγάλο είναι το υπόλοιπο της γραμμής. Η πρώτη γραμμή ξεκινά με 005b, η οποία είναι δεκαεξαδική μορφή του 91, πράγμα που σημαίνει ότι 91 bytes παραμένουν στη γραμμή αυτή. Η επόμενη γραμμή ξεκινά με 003e, δηλαδή 62, οπότε διαβάζουμε τα υπόλοιπα 62 byte. Η επόμενη γραμμή είναι 0000, πράγμα που σημαίνει ότι ο διακομιστής τελειώσε με τη λίστα αναφοράς.

Τώρα που γνωρίζει την κατάσταση του διακομιστή, η διαδικασία send-pack καθορίζει ποιες υποβολές έχει που δεν έχει ο διακομιστής. Για κάθε αναφορά που θα ενημερώσει αυτή η ώθηση, η διαδικασία send-pack αναφέρει αυτήν την πληροφορία στη διαδικασία receive-pack. Για παράδειγμα, εάν ενημερώνουμε τον κλάδο master και προσθέτουμε έναν κλάδο experiment, η απάντηση send-pack θα μοιάζει με κάτι σαν αυτό:

0076ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 \
	refs/heads/master report-status
006c0000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d \
	refs/heads/experiment
0000

Το Git στέλνει μια γραμμή για κάθε αναφορά που ενημερώνουμε με το μήκος της γραμμής, τον παλιό SHA-1, τον νέο SHA-1 και την αναφορά που ενημερώνεται. Η πρώτη γραμμή έχει επίσης τις δυνατότητες του πελάτη. Αν η τιμή SHA-1 αποτελείται μόνο από 0, αυτό σημαίνει ότι δεν υπήρχε τίποτα πριν —επειδή προσθέτουμε την αναφορά του experiment. Εάν διαγράψαμε μια αναφορά, θα δούμε το αντίθετο: όλα τα 0 στη δεξιά πλευρά.

Στη συνέχεια, ο υπολογιστής-πελάτης στέλνει ένα πακέτο από όλα τα αντικείμενα που δεν έχει ακόμα ο διακομιστής. Τέλος, ο διακομιστής αποκρίνεται με μια ένδειξη επιτυχίας (ή αποτυχίας):

000eunpack ok
HTTP(S)

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

=> GET http://server/simplegit-progit.git/info/refs?service=git-receive-pack
001f# service=git-receive-pack
00ab6c5f0e45abd7832bf23074a333f739977c9e8188 refs/heads/master report-status \
	delete-refs side-band-64k quiet ofs-delta \
	agent=git/2:2.1.1~vmg-bitmaps-bugaloo-608-g116744e
0000

Αυτό είναι το τέλος της πρώτης ανταλλαγής πελάτη-εξυπηρετητή. Στη συνέχεια ο πελάτης υποβάλλει ένα άλλο αίτημα, αυτήν τη φορά ένα POST, με τα δεδομένα που παρέχει το git-upload-pack.

=> POST http://server/simplegit-progit.git/git-receive-pack

Το αίτημα POST περιλαμβάνει την έξοδο send-pack και το πακέτο ως το ωφέλιμο φορτίο του. Ο διακομιστής τότε υποδεικνύει επιτυχία ή αποτυχία με την απόκριση HTTP.

Λήψη δεδομένων

Όταν κάνουμε λήψη δεδομένων, εμπλέκονται οι διαδικασίες fetch-pack και upload-pack. Ο πελάτης ξεκινά μια διαδικασία fetch-pack που συνδέεται με μια διαδικασία upload-pack στην απομακρυσμένη πλευρά για να διαπραγματευτεί ποια δεδομένα θα ληφθούν.

SSH

Αν κάνουμε την ανάκτηση μέσω SSH, η fetch-pack τρέχει κάτι τέτοιο:

$ ssh -x git@server "git-upload-pack 'simplegit-progit.git'"

Αφού η fetch-pack συνδεθεί, η upload-pack επιστρέφει κάτι σαν αυτό:

00dfca82a6dff817ec66f44342007202690a93763949 HEAD□multi_ack thin-pack \
	side-band side-band-64k ofs-delta shallow no-progress include-tag \
	multi_ack_detailed symref=HEAD:refs/heads/master \
	agent=git/2:2.1.1+github-607-gfba4028
003fe2409a098dc3e53539a9028a94b6224db9d6a6b6 refs/heads/master
0000

Αυτό είναι πολύ παρόμοιο με αυτό που αποκρίνεται η receive-pack, αλλά οι δυνατότητες είναι διαφορετικές. Επιπλέον, στέλνει πίσω αυτό στο οποίο δείχνει ο HEAD (symref=HEAD:refs/heads/master) έτσι ώστε ο πελάτης να ξέρει τι να ελέγξει εάν αυτό είναι κλώνος.

Σε αυτό το σημείο, η διαδικασία fetch-pack εξετάζει ποια αντικείμενα έχει και ανταποκρίνεται με τα αντικείμενα που χρειάζεται, στέλνοντας want και κατόπιν το SHA-1 που θέλει. Αποστέλλει όλα τα αντικείμενα που έχει ήδη με have και στη συνέχεια το SHA-1. Στο τέλος αυτής της λίστας, γράφει done ώστε να εκκινήσει τη διαδικασία upload-pack και η τελευταία να ξεκινήσει να στέλνει το πακέτο με τα δεδομένα που χρειάζεται:

003cwant ca82a6dff817ec66f44342007202690a93763949 ofs-delta
0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
0009done
0000
HTTP(S)

Η χειραψία για μια λειτουργία ανάκτησης απαιτεί δύο αιτήματα HTTP. Το πρώτο είναι ένα GET στο ίδιο σημείο σύνδεσης που χρησιμοποιείται στο χαζό πρωτόκολλο:

=> GET $GIT_URL/info/refs?service=git-upload-pack
001e# service=git-upload-pack
00e7ca82a6dff817ec66f44342007202690a93763949 HEAD□multi_ack thin-pack \
	side-band side-band-64k ofs-delta shallow no-progress include-tag \
	multi_ack_detailed no-done symref=HEAD:refs/heads/master \
	agent=git/2:2.1.1+github-607-gfba4028
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
0000

Αυτό είναι πολύ παρόμοιο με την κλήση του `git-upload-pack 'μέσω μιας σύνδεσης SSH, αλλά η δεύτερη ανταλλαγή γίνεται ως ξεχωριστό αίτημα:

=> POST $GIT_URL/git-upload-pack HTTP/1.0
0032want 0a53e9ddeaddad63ad106860237bbf53411d11a7
0032have 441b40d833fdfa93eb2908e52742248faf0ee993
0000

Επαναλαμβάνουμε ότι αυτή είναι η ίδια μορφή όπως παραπάνω. Η απάντηση σε αυτό το αίτημα υποδηλώνει επιτυχία ή αποτυχία και περιλαμβάνει το πακέτο.

Περίληψη πρωτοκόλλων

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