
# Escritura en repositorios

Pitmaster escribe repositorios git con los mismos formatos binarios que el binario canónico `git`. Los objetos se escriben en el almacén de objetos sueltos (comprimidos con zlib), el índice se escribe en formato v2 (o v3 cuando las entradas requieren indicadores extendidos; un índice existente conserva su versión) y las referencias se escriben como archivos sueltos. Los datos escritos se pueden verificar con `git fsck --strict`.

## Inicializar un repositorio

```php
use Pitmaster\Pitmaster;

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

Esto crea:
- `.git/objects/` (almacén de objetos vacío)
- `.git/refs/heads/` y `.git/refs/tags/` (directorios de referencias vacíos)
- `.git/HEAD` que contiene `ref: refs/heads/main`
- `.git/hooks/`, `.git/info/exclude` y `.git/description`
- `.git/config` con la configuración predeterminada

Pasa `'sha256'` como segundo argumento para crear un repositorio SHA-256: `Pitmaster::init($path, 'sha256')`.

La configuración predeterminada no establece `core.logallrefupdates`, por lo que Pitmaster no registra entradas de reflog en los repositorios que crea hasta que establezcas esa clave en `true` ([#8](https://github.com/inline0/pitmaster/issues/8)). Los repositorios creados por `git init` ya la tienen configurada.

## Preparación de archivos

### add

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

Cada argumento es una ruta de archivo relativa a la raíz del árbol de trabajo. Los directorios y patrones glob no se expanden, y si falta un archivo se lanza `RuntimeException`. Para cada archivo, Pitmaster:
1. Lee el contenido del archivo del árbol de trabajo
2. Crea un objeto `Blob` y calcula su hash
3. Escribe el blob en `.git/objects/` (comprimido con zlib)
4. Actualiza la entrada del índice para esa ruta con el nuevo hash y los metadatos del archivo

### remove

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

Elimina las rutas rastreadas del índice y del árbol de trabajo. Usa `--cached` para eliminarlas solo del índice, o `-r` / `--recursive` para directorios rastreados.

### mv

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

Renombra el archivo en el árbol de trabajo, elimina la entrada anterior del índice y añade la nueva.

## Crear commits

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

El proceso de commit:

1. **Crear el árbol a partir del índice**: la lista plana de entradas del índice se organiza en una jerarquía de árboles (los directorios se convierten en objetos árbol y los archivos son referencias a blobs). Cada árbol se escribe en el almacén de objetos.
2. **Determinar el padre**: el commit HEAD actual pasa a ser el padre. Los commits raíz (el primer commit de un repositorio) no tienen padres.
3. **Determinar el autor**: se obtiene de las variables de entorno `PITMASTER_AUTHOR_*` y `GIT_AUTHOR_*`, luego de las constantes `PITMASTER_AUTHOR_NAME`/`PITMASTER_AUTHOR_EMAIL`, después de `.git/config` (`user.name`/`user.email`) y, por último, de `Pitmaster <pitmaster@localhost>`. El committer se resuelve de la misma manera.
4. **Escribir el objeto commit**: el contenido del commit (`tree`, `parent`, `author`, `committer`, mensaje) se serializa, se calcula su hash y se escribe.
5. **Actualizar HEAD**: la referencia de rama a la que apunta HEAD se actualiza con el nuevo hash del commit.

Los hooks se ejecutan antes y después de estos pasos (`pre-commit`, `prepare-commit-msg`, `commit-msg`, `post-commit`), salvo que el manejador se haya abierto con `['hooks' => false]`. `commit()` lanza una excepción si falta el mensaje, si el árbol no ha cambiado respecto de HEAD o si el índice tiene rutas sin fusionar.

### Autor personalizado

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

## Verificación con git fsck

Las pruebas de integración de Pitmaster verifican los objetos escritos con `git fsck --strict`, y también puedes verificarlo manualmente:

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

Si Pitmaster escribe un objeto no válido, `git fsck` informará del error de inmediato.

## Administración de ramas

### Crear una rama

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

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

Crea un archivo de referencia en `.git/refs/heads/<name>` que contiene el hash del commit de destino.

### Eliminar una rama

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

### Cambiar de rama

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

Actualiza HEAD a una referencia simbólica que apunta a la rama, restablece el índice para que coincida con el árbol de la punta de la rama y actualiza el árbol de trabajo.

Los cambios locales en rutas que difieren entre los dos árboles detienen el checkout con un `RuntimeException`. Los cambios sin confirmar en los demás archivos rastreados se reemplazan por el contenido de la rama, como haría git con los archivos que conservaría ([#2](https://github.com/inline0/pitmaster/issues/2)). Confírmalos o guárdalos en stash antes de cambiar de rama.

### Comprobar si se ha fusionado

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

## Administración de etiquetas

### Etiqueta ligera

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

Una etiqueta ligera es simplemente una referencia que apunta directamente a un commit.

### Etiqueta anotada

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

Una etiqueta anotada crea un objeto de etiqueta (con etiquetador, mensaje y destino) y una referencia que apunta a él. El etiquetador es la identidad del committer actual, resuelta igual que la del autor del commit.

### Etiquetador personalizado

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

## Actualizar referencias directamente

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

## Escribir objetos directamente

Para casos de uso avanzados, puedes escribir objetos sin pasar por la capa de alto nivel API.

### Escribir un 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!"
```

En un repositorio SHA-256, pasa el algoritmo: `Blob::fromContent('Hello, world!', 'sha256')`.

### Escribir un árbol

```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()` escribe las entradas en el orden indicado y las cadenas de modo tal como se proporcionan. Pásalas en el orden de árbol de git (por nombre, comparando los directorios como si su nombre terminara en `/`) y escribe los modos de directorio como `40000`: git almacena los modos de árbol sin el cero inicial y `git fsck --strict` rechaza `040000`.

### Escribir un 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);
```

## Reset

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

## Restore

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

## Cherry-pick y revert

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

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

Ambos aceptan cualquier revisión que admita `resolve()`. Se admiten hashes completos; los abreviados no.
