Cloner, récupérer, envoyer
Pitmaster prend en charge les trois opérations réseau fondamentales de git : le clonage (téléchargement initial), la récupération (téléchargement incrémentiel) et l’envoi (téléversement). Les URL SSH (ssh://..., user@host:path) utilisent le transport SSH ; les autres URL utilisent HTTP intelligent, et le clonage et la récupération basculent sur HTTP simple lorsque l’échange intelligent échoue (voir Protocoles).
Ces trois opérations démarrent des transports réseau ; elles déclenchent donc RuntimeException sur un handle ouvert avec ['processes' => false] ou Pitmaster::processFreeOptions().
Clonage
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);
Le nouveau dépôt utilise toujours SHA-1. Si le clonage échoue et que la destination n’existait pas auparavant, le répertoire partiellement créé est supprimé.
Déroulement du clonage
- Initialiser : créer un nouveau dépôt vide au chemin de destination
- Configurer le dépôt distant : enregistrer URL sous le nom
remote.origin.urlet configurer le refspec de récupération - Découvrir les références :
GET /info/refs?service=git-upload-packpour connaître le contenu du serveur - Négocier : envoyer les lignes
wantpour toutes les références distantes (hachages dédupliqués) - Recevoir le pack : télécharger le fichier pack contenant tous les objets demandés
- Écrire le pack : enregistrer le fichier pack dans
.git/objects/pack/ - Indexer le pack : générer le fichier
.idxpour accélérer les recherches - Configurer les références : créer des branches de suivi distant (
refs/remotes/origin/*) et des tags - Définir HEAD : pointer HEAD vers la branche par défaut (indiquée par la capacité
symref=HEAD:...du serveur, ourefs/heads/mainsi le serveur n’envoie rien), puis définirbranch.<name>.remoteetbranch.<name>.merge - Extraire : extraire cette branche, ce qui écrit l’arbre de travail et l’index
Les erreurs survenant lors de l’extraction finale sont interceptées et ignorées ; le clonage peut donc renvoyer un arbre de travail vide ou partiel (#19).
$repo = Pitmaster::clone($url, $path);
$head = $repo->head();
echo $head->message;
$branches = $repo->branches();
$files = $repo->status();
Matérialisation de l’arbre de travail
Après avoir reçu le pack, Pitmaster appelle $repo->checkout() pour la branche par défaut. Cette opération écrit dans le répertoire de travail tous les fichiers de l’arbre du commit HEAD et construit l’index à partir des entrées de l’arbre, afin que status() indique un arbre de travail propre.
Récupération
// 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
Déroulement de la récupération
- Lire URL du dépôt distant : depuis
.git/config(remote.<name>.url) - Découvrir les références : obtenir l’état actuel de toutes les références distantes
- Négocier les objets voulus et disponibles :
- Voulus : références distantes correspondant aux refspecs
remote.<name>.fetch(+refs/heads/*:refs/remotes/<name>/*par défaut) dont les objets sont absents en local (toutes si une profondeur est indiquée) - Disponibles : pointe de chaque référence locale
- Voulus : références distantes correspondant aux refspecs
- Recevoir le pack : télécharger uniquement les nouveaux objets
- Écrire le pack : l’enregistrer dans
.git/objects/pack/et créer son index - Mettre à jour les références de suivi : mettre à jour
refs/remotes/<remote>/<branch>pour chaque branche distante correspondante et écrire toutes les références de tags annoncées. Les objets de tags ne sont pas demandés séparément ; un nouveau tag annoté peut donc être écrit comme référence vers un objet qui n’a pas été téléchargé (#24)
// 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() recherche les noms sous les formes refs/heads/<name>, refs/tags/<name> et nom lui-même ; la forme courte origin/main est donc introuvable.
Pitmaster demande des packs allégés, qu’il ne peut pas encore indexer (#16). Si un serveur répond à une récupération incrémentielle par un pack allégé, fetch() déclenche PackParseException.
Déjà à jour
Si tous les objets pointés par les références distantes existent déjà en local, aucun pack n’est demandé et fetch() revient après la découverte des références. Dans ce cas, les références de suivi ne sont pas mises à jour non plus, même si les branches distantes ont avancé vers des commits déjà présents localement (#23).
Envoi
$repo->push(); // Push current branch to 'origin'
$repo->push('origin', 'main'); // Push a specific branch
$repo->push('upstream', 'feature'); // Push to a named remote
Déroulement de l’envoi
- Lire URL du dépôt distant : depuis
.git/config - Déterminer le contenu à envoyer : résoudre la branche locale en son hachage de commit
- Découvrir les références distantes : obtenir l’état actuel de la branche distante (pour calculer l’ancien hachage)
- Vérifier l’avance rapide : si la branche distante n’est pas un ancêtre de la branche locale, déclencher
RuntimeException("Push rejected: non-fast-forward update to <branch>")avant tout envoi - Envoyer la mise à jour de référence :
<old-hash> <new-hash> refs/heads/<branch> - Envoyer les données du pack : pack contenant les objets nécessaires au dépôt distant
- Vérifier le résultat : le serveur renvoie le succès ou l’échec pour chaque référence
Le hook pre-push s’exécute avant l’envoi de la mise à jour, sauf si les hooks sont désactivés.
// Push the current branch
$repo->push();
// Push a specific branch to a specific remote
$repo->push('origin', 'feature/login');
Conditions requises pour l’envoi
- La branche actuelle doit exister en local
- Un dépôt distant doit être configuré avec un URL
- Le dépôt distant doit accepter l’envoi (authentification, autorisations, règles d’avance non rapide)
Envoi forcé
Pitmaster envoie l’ancien hachage issu de l’annonce en direct des références distantes, et push() refuse localement les mises à jour sans avance rapide. Pour écraser une branche distante, utilisez force-with-lease :
// 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 (fonctionnement interne de la récupération)
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);
La négociation des objets voulus et disponibles construit une requête 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
Le serveur répond avec des accusés de réception, puis les données du pack (éventuellement encapsulées dans side-band).
ReceivePackClient (fonctionnement interne de l’envoi)
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);
Format de la requête d’envoi :
<old-hash> <new-hash> refs/heads/main\0<capabilities>\n
0000
PACK<pack data>
Gestion des erreurs
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() peut également déclencher une simple RuntimeException (dépôt distant inconnu, handle sans processus) ou PackParseException (pack impossible à indexer). ProtocolException et PackParseException héritent toutes deux de \RuntimeException ; intercepter \RuntimeException les couvre donc toutes.
Configuration des dépôts distants
Les dépôts distants sont stockés dans .git/config :
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
Accédez-y via le API de configuration :
$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');
Données pack pour l’envoi
Actuellement, Pitmaster collecte les objets accessibles depuis les commits envoyés, mais pas depuis les références actuelles du dépôt distant, puis les écrit dans un pack comme objets de base (sans compression delta). Cette méthode est correcte, mais produit des packs plus volumineux que git push. La compression delta lors de l’écriture des packs est prévue comme amélioration.