
# Gravação em repositórios

Pitmaster grava repositórios git usando os mesmos formatos binários do binário canônico `git`. Os objetos são gravados no armazenamento de objetos soltos (compactados com zlib), o índice é gravado no formato v2 (ou v3 quando as entradas precisam de flags estendidas; um índice existente mantém sua versão), e as refs são gravadas como arquivos soltos. Os dados gravados podem ser verificados com `git fsck --strict`.

## Inicializando um repositório

```php
use Pitmaster\Pitmaster;

$repo = Pitmaster::init('/path/to/new-project');
```

Isso cria:
- `.git/objects/` (armazenamento de objetos vazio)
- `.git/refs/heads/` e `.git/refs/tags/` (diretórios de refs vazios)
- `.git/HEAD` contendo `ref: refs/heads/main`
- `.git/hooks/`, `.git/info/exclude` e `.git/description`
- `.git/config` com as configurações padrão

Passe `'sha256'` como segundo argumento para criar um repositório SHA-256: `Pitmaster::init($path, 'sha256')`.

A configuração padrão não define `core.logallrefupdates`, então Pitmaster não registra entradas no reflog de um repositório que criou até você definir essa chave como `true` ([#8](https://github.com/inline0/pitmaster/issues/8)). Os repositórios criados por `git init` já a definem.

## Preparando arquivos

### add

```php
$repo->add('src/Feature.php');
$repo->add('src/Feature.php', 'tests/FeatureTest.php', 'README.md');
```

Cada argumento é um caminho de arquivo relativo à raiz da árvore de trabalho. Diretórios e padrões glob não são expandidos, e um arquivo ausente gera `RuntimeException`. Para cada arquivo, Pitmaster:
1. Lê o conteúdo do arquivo na árvore de trabalho
2. Cria um objeto `Blob` e calcula seu hash
3. Grava o blob em `.git/objects/` (compactado com zlib)
4. Atualiza a entrada do índice para esse caminho com o novo hash e os metadados do arquivo

### remove

```php
$repo->remove('old-file.txt');
$repo->remove('-r', 'src');
$repo->remove('--cached', 'old-file.txt');
```

Remove os caminhos rastreados do índice e da árvore de trabalho. Use `--cached` para remover apenas do índice, ou `-r` / `--recursive` para diretórios rastreados.

### mv

```php
$repo->mv('old-name.php', 'new-name.php');
```

Renomeia o arquivo na árvore de trabalho, remove a entrada antiga do índice e adiciona a nova.

## Criando commits

```php
$commitId = $repo->commit('Fix null pointer bug');
echo $commitId->hex;
```

O processo de commit:

1. **Criar árvore a partir do índice**: a lista simples de entradas do índice é organizada em uma hierarquia de árvores (diretórios se tornam objetos de árvore, arquivos são referências a blobs). Cada árvore é gravada no armazenamento de objetos.
2. **Determinar o pai**: o commit HEAD atual se torna o pai. Commits raiz (o primeiro commit em um repositório) não têm pais.
3. **Determinar o autor**: obtido das variáveis de ambiente `PITMASTER_AUTHOR_*` e `GIT_AUTHOR_*`, depois das constantes `PITMASTER_AUTHOR_NAME`/`PITMASTER_AUTHOR_EMAIL`, depois de `.git/config` (`user.name`/`user.email`) e, por fim, de `Pitmaster <pitmaster@localhost>`. O committer é determinado da mesma forma.
4. **Gravar o objeto de commit**: o conteúdo do commit (`tree`, `parent`, `author`, `committer`, mensagem) é serializado, tem seu hash calculado e é gravado.
5. **Atualizar o HEAD**: a ref da branch para a qual o HEAD aponta é atualizada com o novo hash do commit.

Os hooks são executados antes e depois dessas etapas (`pre-commit`, `prepare-commit-msg`, `commit-msg`, `post-commit`), a menos que o handle tenha sido aberto com `['hooks' => false]`. `commit()` gera um erro quando a mensagem está ausente, quando a árvore não mudou em relação ao HEAD ou quando o índice contém caminhos não mesclados.

### Autor personalizado

```php
$author = 'Jane Smith <jane@example.com> 1700000000 +0000';
$commitId = $repo->commit('Add feature', $author);
```

## Verificação com fsck de git

Os testes de integração de Pitmaster verificam os objetos gravados em relação a `git fsck --strict`, e você pode fazer a mesma verificação manualmente:

```bash
cd /path/to/repo
git fsck --strict
```

Se Pitmaster gravar um objeto inválido, `git fsck` informará o erro imediatamente.

## Gerenciando branches

### Criar uma branch

```php
// Branch from HEAD
$repo->createBranch('feature/new-thing');

// Branch from a specific commit
$repo->createBranch('hotfix', $repo->resolve('v1.0.0'));
```

Cria um arquivo ref em `.git/refs/heads/<name>` contendo o hash do commit de destino.

### Excluir uma branch

```php
$repo->deleteBranch('feature/old');
```

### Alternar branches

```php
$repo->checkout('feature/login');
```

Atualiza o HEAD para uma ref simbólica que aponta para a branch, redefine o índice para corresponder à árvore do ponto mais recente da branch e atualiza a árvore de trabalho.

Alterações locais nos caminhos que diferem entre as duas árvores interrompem o checkout com um `RuntimeException`. Alterações não commitadas em todos os outros arquivos rastreados são substituídas pelo conteúdo da branch, enquanto git as manteria ([#2](https://github.com/inline0/pitmaster/issues/2)). Faça commit ou guarde-as antes de alternar.

### Verificar se foi mesclada

```php
if ($repo->isBranchMerged('feature/done')) {
    $repo->deleteBranch('feature/done');
}
```

## Gerenciando tags

### Tag leve

```php
$repo->createLightweightTag('v1.0.0');                          // At HEAD
$repo->createLightweightTag('v0.9.0', $repo->resolve('HEAD~5'));
```

Uma tag leve é apenas uma ref que aponta diretamente para um commit.

### Tag anotada

```php
$tagId = $repo->createTag('v2.0.0', 'Release version 2.0.0');
```

Uma tag anotada cria um objeto de tag (com criador da tag, mensagem e destino) e uma ref que aponta para ele. O criador da tag é a identidade atual do committer, determinada da mesma forma que o autor do commit.

### Criador de tag personalizado

```php
$tagId = $repo->createTag(
    'v2.0.0',
    'Release version 2.0.0',
    $repo->resolve('HEAD'),
    'Jane Smith <jane@example.com> 1700000000 +0000',
);
```

## Atualizando refs diretamente

```php
$repo->updateRef('refs/heads/main', $commitId);
$repo->updateRef('refs/tags/latest', $commitId);
```

## Gravando objetos diretamente

Para casos de uso avançados, você pode gravar objetos sem passar pela camada de alto nível API.

### Gravar um blob

```php
use Pitmaster\Object\Blob;

$blob = Blob::fromContent('Hello, world!');
$id = $repo->writeObject($blob);
echo $id->hex;  // SHA-1 of "blob 13\0Hello, world!"
```

Em um repositório SHA-256, percorra o algoritmo: `Blob::fromContent('Hello, world!', 'sha256')`.

### Escrever uma árvore

```php
use Pitmaster\Object\Tree;
use Pitmaster\Object\TreeEntry;
use Pitmaster\Object\ObjectId;

$entries = [
    new TreeEntry('100644', 'README.md', $blobId),
    new TreeEntry('40000', 'src', $subtreeId),
];

$tree = Tree::fromEntries($entries);
$id = $repo->writeObject($tree);
```

`Tree::fromEntries()` grava as entradas na ordem fornecida e as strings de modo como fornecidas. Passe-as na ordem de árvore de git (por nome, comparando diretórios como se o nome terminasse em `/`) e grave os modos de diretório como `40000`: git armazena os modos de árvore sem o zero à esquerda, e `git fsck --strict` rejeita `040000`.

### Escrever um commit

```php
use Pitmaster\Object\Commit;
use Pitmaster\Object\ObjectId;
use Pitmaster\Object\ObjectType;

$content = Commit::buildContent(
    tree: $treeId,
    parents: [$parentId],
    author: 'Name <email> 1700000000 +0000',
    committer: 'Name <email> 1700000000 +0000',
    message: "Commit message\n",
);

$commitId = ObjectId::compute(ObjectType::Commit, $content);
$commit = Commit::parse($content, $commitId);
$repo->writeObject($commit);
```

## Redefinir

```php
// Soft: move HEAD only
$repo->reset('HEAD~1', 'soft');

// Mixed: move HEAD + reset index (default)
$repo->reset('HEAD~1');

// Hard: move HEAD + reset index + reset working tree
$repo->reset('HEAD~3', 'hard');
```

## Restaurar

```php
// Restore a file from the index into the worktree
$repo->restore('src/File.php');

// Restore the index version from HEAD (the worktree is left alone)
$repo->restore('src/File.php', staged: true);

// Restore from a specific commit
$repo->restore('src/File.php', 'HEAD~1');

// Restore both index and worktree from a specific commit
$repo->restore('src/File.php', 'HEAD~1', staged: true, worktree: true);
```

## Aplicar cherry-pick e reverter

```php
// Apply a commit on the current branch
$newId = $repo->cherryPick('feature~1');

// Undo a commit
$revertId = $repo->revert('HEAD~2');
```

Ambos aceitam qualquer revisão que `resolve()` aceite. Hashes completos funcionam; hashes abreviados não.
