Clonar, obtener y enviar
Pitmaster admite las tres operaciones de red fundamentales de git: clonar (descarga inicial), obtener (descarga incremental) y enviar (subida). Las URL SSH (ssh://..., user@host:path) usan el transporte SSH; las demás URL usan HTTP inteligente, y la clonación y la obtención recurren a HTTP simple si falla el intercambio inteligente (consulte Protocolos).
Las tres operaciones inician transportes de red, por lo que generan RuntimeException en un identificador abierto con ['processes' => false] o Pitmaster::processFreeOptions().
Clonar
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);
El nuevo repositorio siempre usa SHA-1. Si la clonación falla y el destino no existía antes, se elimina el directorio creado parcialmente.
Proceso de clonación
- Inicializar: crear un repositorio nuevo y vacío en la ruta de destino
- Configurar el remoto: guardar URL como
remote.origin.urly configurar la especificación de referencia para la obtención - Descubrir referencias:
GET /info/refs?service=git-upload-packpara averiguar qué contiene el servidor - Negociar: enviar líneas
wantpara todas las referencias remotas (deduplicadas por hash) - Recibir el paquete: descargar el archivo de paquete que contiene todos los objetos solicitados
- Escribir el paquete: guardar el archivo de paquete en
.git/objects/pack/ - Indexar el paquete: generar el archivo
.idxpara agilizar las búsquedas - Configurar las referencias: crear ramas de seguimiento remoto (
refs/remotes/origin/*) y etiquetas - Establecer HEAD: apuntar HEAD a la rama predeterminada (según la capacidad
symref=HEAD:...del servidor, orefs/heads/mainsi el servidor no envía ninguna) y establecerbranch.<name>.remoteybranch.<name>.merge - Cambiar de rama: cambiar a esa rama, lo que escribe el árbol de trabajo y el índice
Los errores durante el cambio de rama final se capturan y se ignoran, por lo que la clonación puede devolver un árbol de trabajo vacío o parcial (#19).
$repo = Pitmaster::clone($url, $path);
$head = $repo->head();
echo $head->message;
$branches = $repo->branches();
$files = $repo->status();
Creación del árbol de trabajo
Después de recibir el paquete, Pitmaster llama a $repo->checkout() para la rama predeterminada. Esto escribe todos los archivos del árbol de la confirmación HEAD en el directorio de trabajo y crea el índice a partir de las entradas del árbol, de modo que status() muestra un árbol de trabajo limpio.
Obtener
// 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
Proceso de obtención
- Leer el remoto URL: desde
.git/config(remote.<name>.url) - Descubrir referencias: obtener el estado actual de todas las referencias remotas
- Negociar lo que se necesita y lo que se tiene:
- Se necesita: referencias remotas que coincidan con las especificaciones
remote.<name>.fetch(predeterminadas+refs/heads/*:refs/remotes/<name>/*) cuyos objetos falten localmente (todas cuando se indique una profundidad) - Se tiene: cada punta de referencia local
- Se necesita: referencias remotas que coincidan con las especificaciones
- Recibir el paquete: descargar solo los objetos nuevos
- Escribir el paquete: guardar en
.git/objects/pack/y crear su índice - Actualizar las referencias de seguimiento: actualizar
refs/remotes/<remote>/<branch>para cada rama remota coincidente y escribir cada referencia de etiqueta anunciada. Los objetos de etiqueta no se solicitan por separado, por lo que una etiqueta anotada nueva puede escribirse como referencia a un objeto que no se descargó (#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() busca los nombres como refs/heads/<name>, refs/tags/<name> y el propio nombre, por lo que no encuentra la forma abreviada origin/main.
Pitmaster solicita paquetes delgados, que todavía no puede indexar (#16). Si un servidor responde a una obtención incremental con un paquete delgado, fetch() genera PackParseException.
Ya está actualizado
Si todos los objetos a los que apuntan las referencias remotas ya existen localmente, no se solicita ningún paquete y fetch() termina después de descubrir las referencias. En ese caso tampoco se actualizan las referencias de seguimiento, aunque las ramas remotas hayan avanzado a confirmaciones que ya tiene (#23).
Enviar
$repo->push(); // Push current branch to 'origin'
$repo->push('origin', 'main'); // Push a specific branch
$repo->push('upstream', 'feature'); // Push to a named remote
Proceso de envío
- Leer el remoto URL: desde
.git/config - Determinar qué enviar: resolver la rama local para obtener su hash de confirmación
- Descubrir las referencias remotas: obtener el estado actual de la rama remota (para calcular el hash anterior)
- Comprobar el avance rápido: si la rama remota no es antecesora de la rama local, generar
RuntimeException("Push rejected: non-fast-forward update to <branch>")antes de enviar nada - Enviar la actualización de referencia:
<old-hash> <new-hash> refs/heads/<branch> - Enviar los datos del paquete: paquete con los objetos que necesita el remoto
- Comprobar el resultado: el servidor responde con el éxito o el fallo de cada referencia
El hook pre-push se ejecuta antes de enviar la actualización, salvo que los hooks estén desactivados.
// Push the current branch
$repo->push();
// Push a specific branch to a specific remote
$repo->push('origin', 'feature/login');
Requisitos para enviar
- La rama actual debe existir localmente
- Debe haber un remoto configurado con un URL
- El remoto debe aceptar el envío (autenticación, permisos y políticas de avance no rápido)
Envío forzado
Pitmaster envía el hash anterior del anuncio de referencias activas del remoto y push() rechaza localmente las actualizaciones que no sean de avance rápido. Para sobrescribir una rama remota, use 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 (detalles internos de la obtención)
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 negociación de lo que se necesita y lo que se tiene crea una solicitud 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
El servidor responde con acuses de recibo y luego con los datos del paquete (posiblemente encapsulados en side-band).
ReceivePackClient (detalles internos del envío)
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);
Formato de la solicitud de envío:
<old-hash> <new-hash> refs/heads/main\0<capabilities>\n
0000
PACK<pack data>
Gestión de errores
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() también puede generar un RuntimeException simple (remoto desconocido, identificador sin procesos) o PackParseException (un paquete que no se puede indexar). Tanto ProtocolException como PackParseException extienden \RuntimeException, así que capturar \RuntimeException los cubre a todos.
Configuración del remoto
Los remotos se almacenan en .git/config:
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
Acceso mediante la API de configuración:
$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');
Datos del paquete para el envío
Actualmente, Pitmaster recopila los objetos accesibles desde las confirmaciones que se envían y que no lo son desde las referencias actuales del remoto, y los escribe como objetos base (sin compresión delta) en un paquete. Esto es funcionalmente correcto, pero genera paquetes más grandes que git push. La compresión delta al escribir paquetes es una optimización prevista.