
# Zapisywanie w repozytoriach

Pitmaster zapisuje repozytoria git, używając tych samych formatów binarnych co kanoniczny plik binarny `git`. Obiekty są zapisywane w magazynie luźnych obiektów (kompresowane zlib), indeks jest zapisywany w formacie v2 (lub v3, gdy wpisy wymagają rozszerzonych flag; istniejący indeks zachowuje swoją wersję), a referencje są zapisywane jako luźne pliki. Zapisane dane można zweryfikować za pomocą `git fsck --strict`.

## Inicjalizowanie repozytorium

```php
use Pitmaster\Pitmaster;

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

To tworzy:
- `.git/objects/` (pusty magazyn obiektów)
- `.git/refs/heads/` i `.git/refs/tags/` (puste katalogi referencji)
- `.git/HEAD` zawierający `ref: refs/heads/main`
- `.git/hooks/`, `.git/info/exclude` i `.git/description`
- `.git/config` z ustawieniami domyślnymi

Przekaż `'sha256'` jako drugi argument, aby utworzyć repozytorium SHA-256: `Pitmaster::init($path, 'sha256')`.

Domyślna konfiguracja nie ustawia `core.logallrefupdates`, więc Pitmaster nie zapisuje żadnych wpisów refloga w utworzonym repozytorium, dopóki ten klucz nie zostanie ustawiony na `true` ([#8](https://github.com/inline0/pitmaster/issues/8)). Repozytoria utworzone przez `git init` mają już tę wartość ustawioną.

## Dodawanie plików do obszaru stagingu

### add

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

Każdy argument to ścieżka pliku względem katalogu głównego drzewa roboczego. Katalogi i wzorce glob nie są rozwijane, a brakujący plik powoduje zgłoszenie `RuntimeException`. Dla każdego pliku Pitmaster:
1. Odczytuje zawartość pliku z drzewa roboczego
2. Tworzy obiekt `Blob` i oblicza jego hash
3. Zapisuje blob w `.git/objects/` (kompresowany zlib)
4. Aktualizuje wpis indeksu dla tej ścieżki, zapisując nowy hash i metadane pliku

### remove

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

Usuwa śledzone ścieżki z indeksu i drzewa roboczego. Użyj `--cached`, aby usunąć tylko z indeksu, lub `-r` / `--recursive` dla śledzonych katalogów.

### mv

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

Zmienia nazwę pliku w drzewie roboczym, usuwa stary wpis indeksu i dodaje nowy.

## Tworzenie commitów

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

Proces tworzenia commita:

1. **Tworzenie drzewa z indeksu**: płaska lista wpisów indeksu jest organizowana w hierarchię drzewa (katalogi stają się obiektami drzewa, a pliki referencjami do blobów). Każde drzewo jest zapisywane w magazynie obiektów.
2. **Ustalanie rodzica**: bieżący commit HEAD staje się rodzicem. Commity główne (pierwszy commit w repozytorium) nie mają rodziców.
3. **Ustalanie autora**: autor jest pobierany ze zmiennych środowiskowych `PITMASTER_AUTHOR_*` i `GIT_AUTHOR_*`, następnie ze stałych `PITMASTER_AUTHOR_NAME`/`PITMASTER_AUTHOR_EMAIL`, potem z `.git/config` (`user.name`/`user.email`), a na końcu z `Pitmaster <pitmaster@localhost>`. Tożsamość commitera jest ustalana w ten sam sposób.
4. **Zapisywanie obiektu commit**: zawartość commita (`tree`, `parent`, `author`, `committer`, komunikat) jest serializowana, hashowana i zapisywana.
5. **Aktualizacja HEAD**: referencja gałęzi wskazywana przez HEAD jest aktualizowana do nowego hasha commita.

Hooki uruchamiają się przed tymi krokami i po nich (`pre-commit`, `prepare-commit-msg`, `commit-msg`, `post-commit`), chyba że uchwyt otwarto z `['hooks' => false]`. `commit()` zgłasza błąd, gdy brakuje komunikatu, drzewo nie zmieniło się względem HEAD lub indeks zawiera ścieżki z nierozwiązanymi scaleniami.

### Niestandardowy autor

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

## Weryfikacja za pomocą git fsck

Testy integracyjne Pitmaster sprawdzają zapisane obiekty za pomocą `git fsck --strict`; możesz też zweryfikować je ręcznie:

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

Jeśli Pitmaster zapisze nieprawidłowy obiekt, `git fsck` natychmiast zgłosi błąd.

## Zarządzanie gałęziami

### Tworzenie gałęzi

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

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

Tworzy plik referencji w `.git/refs/heads/<name>` zawierający hash docelowego commita.

### Usuwanie gałęzi

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

### Przełączanie gałęzi

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

Aktualizuje HEAD do symbolicznej referencji wskazującej gałąź, resetuje indeks tak, aby odpowiadał drzewu na końcu gałęzi, i aktualizuje drzewo robocze.

Lokalne zmiany w ścieżkach różniących się między tymi drzewami przerywają checkout z `RuntimeException`. Niezatwierdzone zmiany we wszystkich pozostałych śledzonych plikach są zastępowane zawartością gałęzi, zgodnie z zachowaniem git ([#2](https://github.com/inline0/pitmaster/issues/2)). Zatwierdź je lub odłóż przed przełączeniem.

### Sprawdzanie, czy gałąź została scalona

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

## Zarządzanie tagami

### Tag lekki

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

Tag lekki to po prostu referencja wskazująca bezpośrednio na commit.

### Tag z adnotacją

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

Tag z adnotacją tworzy obiekt tagu (z autorem tagu, komunikatem i celem) oraz referencję, która na niego wskazuje. Autor tagu to bieżąca tożsamość commitera, ustalana tak samo jak autor commita.

### Niestandardowy autor tagu

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

## Bezpośrednia aktualizacja referencji

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

## Bezpośrednie zapisywanie obiektów

W zaawansowanych zastosowaniach można zapisywać obiekty bez użycia interfejsu wysokiego poziomu API.

### Zapis blobu

```php
use Pitmaster\Object\Blob;

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

W repozytorium SHA-256 przekaż algorytm: `Blob::fromContent('Hello, world!', 'sha256')`.

### Zapis drzewa

```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()` zapisuje wpisy w podanej kolejności i używa podanych ciągów trybu. Przekaż je w kolejności drzewa git (według nazwy, przy czym katalogi są porównywane tak, jakby ich nazwa kończyła się na `/`) i zapisuj tryby katalogów jako `40000`: git przechowuje tryby drzew bez początkowego zera, a `git fsck --strict` odrzuca `040000`.

### Zapis commita

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

## Przywracanie

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

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

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

Obie operacje przyjmują dowolną rewizję akceptowaną przez `resolve()`. Pełne hashe działają, skrócone nie.
