Μετάβαση στο περιεχόμενο

Κλωνοποίηση, λήψη, αποστολή

Το Pitmaster υποστηρίζει τις τρεις βασικές λειτουργίες δικτύου του git: clone (αρχική λήψη), fetch (σταδιακή λήψη) και push (αποστολή). Οι διευθύνσεις URL SSH (ssh://..., user@host:path) χρησιμοποιούν το transport SSH· άλλες διευθύνσεις URL χρησιμοποιούν smart HTTP, ενώ το clone και το fetch επιστρέφουν στο dumb HTTP όταν αποτυγχάνει η ανταλλαγή smart (βλ. Πρωτόκολλα).

Και οι τρεις εκκινούν transports δικτύου, επομένως προκαλούν RuntimeException σε handle που ανοίχτηκε με ['processes' => false] ή Pitmaster::processFreeOptions().

Κλωνοποίηση

PHP
use Pitmaster\Pitmaster;

// public static function clone(string $url, string $path, ?int $depth = null, array $options = []): Repository
$repo = Pitmaster::clone('https://github.com/user/repo.git', '/path/to/destination');
$repo = Pitmaster::clone('https://github.com/user/repo.git', '/path/to/shallow', 1);

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

Ροή clone

  1. Αρχικοποίηση: δημιουργήστε ένα νέο κενό αποθετήριο στη διαδρομή προορισμού
  2. Διαμόρφωση απομακρυσμένου αποθετηρίου: αποθηκεύστε το URL ως remote.origin.url και ρυθμίστε το fetch refspec
  3. Εντοπισμός refs: GET /info/refs?service=git-upload-pack για να μάθετε τι περιέχει ο διακομιστής
  4. Διαπραγμάτευση: στείλτε γραμμές want για όλα τα απομακρυσμένα refs (με αφαίρεση διπλοτύπων βάσει hash)
  5. Λήψη pack: κατεβάστε το αρχείο pack που περιέχει όλα τα ζητούμενα αντικείμενα
  6. Εγγραφή pack: αποθηκεύστε το αρχείο pack στο .git/objects/pack/
  7. Δημιουργία index pack: δημιουργήστε το αρχείο .idx για γρήγορες αναζητήσεις
  8. Ρύθμιση refs: δημιουργήστε branches παρακολούθησης απομακρυσμένων (refs/remotes/origin/*) και tags
  9. Ορισμός HEAD: δείξτε το HEAD στο προεπιλεγμένο branch (από τη δυνατότητα symref=HEAD:... του διακομιστή ή από το refs/heads/main αν ο διακομιστής δεν στείλει καμία) και ορίστε τα branch.<name>.remote και branch.<name>.merge
  10. Checkout: κάντε checkout αυτού του branch, το οποίο γράφει το δέντρο εργασίας και το index

Τα σφάλματα κατά το τελικό checkout εντοπίζονται και αγνοούνται, οπότε το clone μπορεί να επιστρέψει με κενό ή μερικό δέντρο εργασίας (#19).

PHP
$repo = Pitmaster::clone($url, $path);
$head = $repo->head();
echo $head->message;

$branches = $repo->branches();
$files = $repo->status();

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

Μετά τη λήψη του pack, το Pitmaster καλεί το $repo->checkout() για το προεπιλεγμένο branch. Αυτό γράφει κάθε αρχείο του δέντρου του commit HEAD στον κατάλογο εργασίας και δημιουργεί το index από τις εγγραφές του δέντρου, ώστε το status() να αναφέρει καθαρό δέντρο εργασίας.

Λήψη

PHP
// public function fetch(string $remote = 'origin', ?int $depth = null): void
$repo->fetch();              // Fetch from 'origin'
$repo->fetch('upstream');    // Fetch from a named remote
$repo->fetch('origin', 1);   // Shallow fetch

Ροή fetch

  1. Ανάγνωση απομακρυσμένου URL: από το .git/config (remote.<name>.url)
  2. Εντοπισμός refs: λήψη της τρέχουσας κατάστασης όλων των απομακρυσμένων refs
  3. Διαπραγμάτευση wants/haves:
    • Want: απομακρυσμένα refs που αντιστοιχούν στα refspecs remote.<name>.fetch (προεπιλογή +refs/heads/*:refs/remotes/<name>/*) και των οποίων τα αντικείμενα λείπουν τοπικά (όλα όταν έχει δοθεί βάθος)
    • Have: κάθε τοπική κορυφή ref
  4. Λήψη pack: κατεβάστε μόνο τα νέα αντικείμενα
  5. Εγγραφή pack: αποθηκεύστε στο .git/objects/pack/ και δημιουργήστε το index του
  6. Ενημέρωση refs παρακολούθησης: ενημερώστε το refs/remotes/<remote>/<branch> για κάθε αντιστοιχισμένο απομακρυσμένο branch και γράψτε κάθε διαφημιζόμενο ref tag. Τα αντικείμενα tag δεν ζητούνται ξεχωριστά, επομένως ένα νέο annotated tag μπορεί να γραφτεί ως ref σε αντικείμενο που δεν έχει ληφθεί (#24)
PHP
// Fetch is incremental: only downloads what's new
$repo->fetch();

// After fetching, merge or rebase to incorporate changes (use the full ref name)
$result = $repo->merge('refs/remotes/origin/main');

Το resolve() αναζητά ονόματα ως refs/heads/<name>, refs/tags/<name> και το ίδιο το όνομα, επομένως δεν εντοπίζεται η σύντομη μορφή origin/main.

Το Pitmaster ζητά thin packs, τα οποία δεν μπορεί ακόμη να καταχωρίσει στο index (#16). Αν ένας διακομιστής απαντήσει σε incremental fetch με thin pack, το fetch() προκαλεί PackParseException.

Ήδη ενημερωμένο

Αν όλα τα αντικείμενα στα οποία δείχνουν τα απομακρυσμένα refs υπάρχουν ήδη τοπικά, δεν ζητείται pack και το fetch() επιστρέφει μετά τον εντοπισμό των refs. Σε αυτή την περίπτωση δεν ενημερώνονται ούτε τα refs παρακολούθησης, ακόμη κι αν τα απομακρυσμένα branches μετακινήθηκαν σε commit που έχετε ήδη (#23).

Αποστολή

PHP
$repo->push();                        // Push current branch to 'origin'
$repo->push('origin', 'main');        // Push a specific branch
$repo->push('upstream', 'feature');   // Push to a named remote

Ροή push

  1. Ανάγνωση απομακρυσμένου URL: από το .git/config
  2. Προσδιορισμός όσων θα σταλούν: επιλύστε το τοπικό branch στο hash του commit του
  3. Εντοπισμός απομακρυσμένων refs: λάβετε την τρέχουσα κατάσταση του απομακρυσμένου branch (για να υπολογίσετε το παλιό hash)
  4. Έλεγχος fast-forward: αν το απομακρυσμένο branch δεν είναι πρόγονος του τοπικού, προκαλέστε RuntimeException("Push rejected: non-fast-forward update to <branch>") πριν από οποιαδήποτε αποστολή
  5. Αποστολή ενημέρωσης ref: <old-hash> <new-hash> refs/heads/<branch>
  6. Αποστολή δεδομένων pack: pack με τα αντικείμενα που χρειάζεται το απομακρυσμένο αποθετήριο
  7. Έλεγχος αποτελέσματος: ο διακομιστής απαντά με επιτυχία ή αποτυχία για κάθε ref

Το hook pre-push εκτελείται πριν σταλεί η ενημέρωση, εκτός αν τα hooks είναι απενεργοποιημένα.

PHP
// Push the current branch
$repo->push();

// Push a specific branch to a specific remote
$repo->push('origin', 'feature/login');

Προϋποθέσεις push

  • Το τρέχον branch πρέπει να υπάρχει τοπικά
  • Πρέπει να έχει διαμορφωθεί απομακρυσμένο αποθετήριο με URL
  • Το απομακρυσμένο αποθετήριο πρέπει να αποδέχεται το push (έλεγχος ταυτότητας, δικαιώματα, πολιτικές non-fast-forward)

Αναγκαστική αποστολή

Το Pitmaster στέλνει το παλιό hash από τη ζωντανή διαφήμιση ref του απομακρυσμένου αποθετηρίου και το push() απορρίπτει τοπικά ενημερώσεις non-fast-forward. Για να αντικαταστήσετε ένα απομακρυσμένο branch, χρησιμοποιήστε force-with-lease:

PHP
// Lease defaults to refs/remotes/<remote>/<branch>; pass an ObjectId to set it explicitly
$repo->pushForceWithLease('origin', 'main');
$repo->pushForceWithLease('origin', 'main', $repo->resolve('refs/remotes/origin/main'));

// Update several branches in one atomic request
$repo->pushAtomic('origin', ['main', 'release']);

// Make the remote mirror local refs (also deletes remote refs that no longer exist locally)
$repo->pushMirror('origin');

UploadPackClient (εσωτερικά του fetch)

PHP
use Pitmaster\Protocol\UploadPackClient;
use Pitmaster\Protocol\SmartHttpClient;

$http = new SmartHttpClient();
$uploadPack = new UploadPackClient($http);

// Fetch with explicit wants and haves
$packData = $uploadPack->fetch($url, $wants, $haves);

Η διαπραγμάτευση want/have δημιουργεί αίτημα pkt-line:

Κώδικας
want <hash1> <capabilities>\n
want <hash2>\n
deepen <depth>\n        (only when a depth is given)
0000
have <hash3>\n
have <hash4>\n
done\n

Ο διακομιστής απαντά με επιβεβαιώσεις και έπειτα με τα δεδομένα pack (ενδεχομένως ενσωματωμένα σε side-band).

ReceivePackClient (εσωτερικά του push)

PHP
use Pitmaster\Protocol\ReceivePackClient;
use Pitmaster\Protocol\SmartHttpClient;

$http = new SmartHttpClient();
$receivePack = new ReceivePackClient($http);

$receivePack->push($url, [
    [
        'old' => $oldId,      // Current remote hash (or zero hash for new branch)
        'new' => $localId,    // Local commit hash to push
        'ref' => 'refs/heads/main',
    ],
], $packData);

Η μορφή του αιτήματος push:

Κώδικας
<old-hash> <new-hash> refs/heads/main\0<capabilities>\n
0000
PACK<pack data>

Χειρισμός σφαλμάτων

PHP
use Pitmaster\Exceptions\ProtocolException;

try {
    $repo->fetch();
} catch (ProtocolException $e) {
    // Network unreachable, auth failed, server error, etc.
    echo $e->getMessage();
}

try {
    $repo->push();
} catch (\RuntimeException $e) {
    // Remote not configured, not on a branch, branch not found, non-fast-forward
    echo $e->getMessage();
}

Το fetch() μπορεί επίσης να προκαλέσει ένα απλό RuntimeException (άγνωστο απομακρυσμένο αποθετήριο, handle χωρίς διεργασίες) ή PackParseException (pack που δεν μπορεί να καταχωριστεί στο index). Τα ProtocolException και PackParseException επεκτείνουν και τα δύο το \RuntimeException, επομένως με τη σύλληψη του \RuntimeException καλύπτονται όλα.

Διαμόρφωση απομακρυσμένων αποθετηρίων

Τα απομακρυσμένα αποθετήρια αποθηκεύονται στο .git/config:

Ini
[remote "origin"]
    url = https://github.com/user/repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*

Πρόσβαση μέσω του API διαμόρφωσης:

PHP
$config = $repo->config();
$url = $config->get('remote.origin.url');
$config->set('remote.upstream.url', 'https://github.com/other/repo.git');
$config->set('remote.upstream.fetch', '+refs/heads/*:refs/remotes/upstream/*');

// set() changes the in-memory config only; write it to persist the change
$config->writeToFile($repo->commonGitDir() . '/config');

Δεδομένα pack για push

Προς το παρόν, το Pitmaster συγκεντρώνει τα αντικείμενα στα οποία είναι προσβάσιμα από τα commit που αποστέλλονται και τα οποία δεν είναι προσβάσιμα από τα τρέχοντα refs του απομακρυσμένου αποθετηρίου, και τα γράφει σε pack ως βασικά αντικείμενα (χωρίς συμπίεση delta). Αυτό είναι λειτουργικά σωστό, αλλά παράγει μεγαλύτερα pack από το git push. Η συμπίεση delta κατά την εγγραφή pack είναι προγραμματισμένη βελτιστοποίηση.