
# Clonar, buscar, enviar

Pitmaster oferece suporte às três operações fundamentais de rede do git: clone (download inicial), fetch (download incremental) e push (upload). URLs SSH (`ssh://...`, `user@host:path`) usam o transporte SSH; outras URLs usam HTTP inteligente, e clone e fetch recorrem a HTTP simples quando a troca inteligente falha (consulte [Protocolos](https://pitmaster.inline0.dev/pt/docs/network/protocols)).

As três iniciam transportes de rede, portanto lançam `RuntimeException` em um identificador aberto com `['processes' => false]` ou `Pitmaster::processFreeOptions()`.

## Clonar

```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);
```

O novo repositório sempre usa SHA-1. Se o clone falhar e o destino não existia antes, o diretório parcialmente criado será removido.

### Fluxo de clonagem

1. **Inicializar**: criar um novo repositório vazio no caminho de destino
2. **Configurar remoto**: salvar o URL como `remote.origin.url` e configurar o refspec de fetch
3. **Descobrir refs**: `GET /info/refs?service=git-upload-pack` para saber o que o servidor tem
4. **Negociar**: enviar linhas `want` para todas as refs remotas (deduplicadas por hash)
5. **Receber pack**: baixar o arquivo pack que contém todos os objetos solicitados
6. **Gravar pack**: salvar o arquivo pack em `.git/objects/pack/`
7. **Indexar pack**: gerar o arquivo `.idx` para pesquisas rápidas
8. **Configurar refs**: criar branches de rastreamento remoto (`refs/remotes/origin/*`) e tags
9. **Definir HEAD**: apontar HEAD para a branch padrão (da capacidade `symref=HEAD:...` do servidor ou `refs/heads/main` quando o servidor não enviar nenhuma) e definir `branch.<name>.remote` e `branch.<name>.merge`
10. **Fazer checkout**: fazer checkout dessa branch, gravando a árvore de trabalho e o índice

Erros durante o checkout final são capturados e ignorados, então um clone pode terminar com uma árvore de trabalho vazia ou parcial ([#19](https://github.com/inline0/pitmaster/issues/19)).

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

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

### Materialização da árvore de trabalho

Após receber o pack, Pitmaster chama `$repo->checkout()` para a branch padrão. Isso grava todos os arquivos da árvore do commit de HEAD no diretório de trabalho e cria o índice a partir das entradas da árvore, para que `status()` informe uma árvore de trabalho limpa.

## Buscar

```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
```

### Fluxo de busca

1. **Ler o URL remoto**: de `.git/config` (`remote.<name>.url`)
2. **Descobrir refs**: obter o estado atual de todas as refs remotas
3. **Negociar wants/haves**:
   - Want: refs remotas correspondentes aos refspecs `remote.<name>.fetch` (padrão `+refs/heads/*:refs/remotes/<name>/*`) cujos objetos estejam ausentes localmente (todas quando uma profundidade é especificada)
   - Have: ponta de cada ref local
4. **Receber pack**: baixar apenas os objetos novos
5. **Gravar pack**: salvar em `.git/objects/pack/` e criar seu índice
6. **Atualizar refs de rastreamento**: atualizar `refs/remotes/<remote>/<branch>` para cada branch remota correspondente e gravar cada ref de tag anunciada. Objetos de tag não são solicitados separadamente, então uma nova tag anotada pode ser gravada como uma ref para um objeto que não foi baixado ([#24](https://github.com/inline0/pitmaster/issues/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()` procura os nomes como `refs/heads/<name>`, `refs/tags/<name>` e o próprio nome, por isso a forma abreviada `origin/main` não é encontrada.

Pitmaster solicita packs finos, que ainda não consegue indexar ([#16](https://github.com/inline0/pitmaster/issues/16)). Um servidor que responde a um fetch incremental com um pack fino faz `fetch()` lançar `PackParseException`.

### Já atualizado

Se todos os objetos para os quais as refs remotas apontam já existirem localmente, nenhum pack é solicitado e `fetch()` retorna após descobrir as refs. Nesse caso, as refs de rastreamento também não são atualizadas, mesmo que as branches remotas tenham avançado para commits que você já possui ([#23](https://github.com/inline0/pitmaster/issues/23)).

## Enviar

```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
```

### Fluxo de envio

1. **Ler o URL remoto**: de `.git/config`
2. **Determinar o que enviar**: resolver a branch local para seu hash de commit
3. **Descobrir refs remotas**: obter o estado atual da branch remota (para calcular o hash antigo)
4. **Verificar fast-forward**: se a branch remota não for ancestral da branch local, lançar `RuntimeException("Push rejected: non-fast-forward update to <branch>")` antes de enviar qualquer coisa
5. **Enviar atualização da ref**: `<old-hash> <new-hash> refs/heads/<branch>`
6. **Enviar dados do pack**: pack contendo os objetos necessários ao remoto
7. **Verificar o resultado**: o servidor responde com sucesso/falha para cada ref

O hook `pre-push` é executado antes do envio da atualização, a menos que os hooks estejam desativados.

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

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

### Requisitos de envio

- A branch atual deve existir localmente
- Um remoto deve estar configurado com um URL
- O remoto deve aceitar o push (autenticação, permissões, políticas de não fast-forward)

### Push forçado

Pitmaster envia o hash antigo do anúncio de refs ativo do remoto, e `push()` recusa atualizações que não sejam fast-forward localmente. Para sobrescrever uma branch remota, use 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 (internals de 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);
```

A negociação want/have cria uma solicitação 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
```

O servidor responde com confirmações e, em seguida, os dados do pack (possivelmente encapsulados em side-band).

## ReceivePackClient (internals de 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);
```

O formato da solicitação de push:

```
<old-hash> <new-hash> refs/heads/main\0<capabilities>\n
0000
PACK<pack data>
```

## Tratamento de erros

```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()` também pode lançar uma `RuntimeException` simples (remoto desconhecido, handle sem processo) ou uma `PackParseException` (um pack que não pode ser indexado). `ProtocolException` e `PackParseException` estendem `\RuntimeException`, então capturar `\RuntimeException` cobre todas elas.

## Configuração de remotos

Os remotos são armazenados em `.git/config`:

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

Acesse pela configuração 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');
```

## Dados do pack para push

Atualmente, Pitmaster coleta os objetos alcançáveis a partir dos commits enviados que não são alcançáveis pelas refs atuais do remoto e os grava em um pack como objetos-base (sem compressão delta). Isso funciona corretamente, mas gera packs maiores que `git push`. A compressão delta na gravação de packs é uma otimização planejada.
