
# Записване в хранилища

Pitmaster записва git хранилища, като използва същите двоични формати като каноничния двоичен файл `git`. Обектите се записват в хранилището за свободни обекти (компресирани със zlib), индексът се записва във формат v2 (или v3, когато записите изискват разширени флагове; съществуващ индекс запазва версията си), а препратките се записват като свободни файлове. Записаните данни могат да се проверят с `git fsck --strict`.

## Инициализиране на хранилище

```php
use Pitmaster\Pitmaster;

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

Това създава:
- `.git/objects/` (празно хранилище за обекти)
- `.git/refs/heads/` и `.git/refs/tags/` (празни директории за препратки)
- `.git/HEAD`, съдържащ `ref: refs/heads/main`
- `.git/hooks/`, `.git/info/exclude` и `.git/description`
- `.git/config` с настройки по подразбиране

Подайте `'sha256'` като втори аргумент, за да създадете хранилище SHA-256: `Pitmaster::init($path, 'sha256')`.

Конфигурацията по подразбиране не задава `core.logallrefupdates`, така че Pitmaster не записва записи в reflog в създаденото хранилище, докато не зададете този ключ на `true` ([#8](https://github.com/inline0/pitmaster/issues/8)). Хранилищата, създадени от `git init`, вече имат тази настройка.

## Подготовка на файлове

### добавяне

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

Всеки аргумент е път до файл спрямо основната директория на работното дърво. Директориите и glob шаблоните не се разширяват, а при липсващ файл се генерира `RuntimeException`. За всеки файл Pitmaster:
1. Чете съдържанието на файла от работното дърво
2. Създава обект `Blob` и изчислява хеша му
3. Записва blob-а в `.git/objects/` (компресиран със zlib)
4. Актуализира записа в индекса за този път с новия хеш и метаданните на файла

### премахване

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

Премахва проследяваните пътища от индекса и работното дърво. Използвайте `--cached`, за да премахнете само от индекса, или `-r` / `--recursive` за проследявани директории.

### преместване

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

Преименува файла в работното дърво, премахва стария запис от индекса и добавя новия.

## Създаване на commit-и

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

Процесът на commit:

1. **Изграждане на дърво от индекса**: плоският списък със записи в индекса се организира в йерархия от дървета (директориите стават обекти дърво, а файловете — препратки към blob-ове). Всяко дърво се записва в хранилището за обекти.
2. **Определяне на родителя**: текущият commit HEAD става родител. Началните commit-и (първият commit в хранилище) нямат родители.
3. **Определяне на автора**: взема се от променливите на средата `PITMASTER_AUTHOR_*` и `GIT_AUTHOR_*`, след това от константите `PITMASTER_AUTHOR_NAME`/`PITMASTER_AUTHOR_EMAIL`, после от `.git/config` (`user.name`/`user.email`) и накрая от `Pitmaster <pitmaster@localhost>`. Комитърът се определя по същия начин.
4. **Записване на обекта commit**: съдържанието на commit-а (`tree`, `parent`, `author`, `committer`, съобщението) се сериализира, хешира и записва.
5. **Актуализиране на HEAD**: препратката към клона, към която сочи HEAD, се актуализира с хеша на новия commit.

Hook-овете се изпълняват преди и след тези стъпки (`pre-commit`, `prepare-commit-msg`, `commit-msg`, `post-commit`), освен ако манипулаторът не е отворен с `['hooks' => false]`. `commit()` генерира грешка, ако липсва съобщение, ако дървото е непроменено спрямо HEAD или ако индексът съдържа несляти пътища.

### Персонализиран автор

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

## Проверка с git fsck

Интеграционните тестове на Pitmaster проверяват записаните обекти спрямо `git fsck --strict`, а същата проверка можете да направите ръчно:

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

Ако Pitmaster запише невалиден обект, `git fsck` веднага ще съобщи за грешката.

## Управление на клонове

### Създаване на клон

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

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

Създава файл за препратка в `.git/refs/heads/<name>`, съдържащ хеша на целевия commit.

### Изтриване на клон

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

### Превключване на клонове

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

Актуализира HEAD така, че да сочи символично към клона, нулира индекса, за да съответства на дървото при върха на клона и актуализира работното дърво.

Локалните промени в пътища, които се различават между двете дървета, спират превключването с `RuntimeException`. Незавършените промени във всички други проследявани файлове се заменят със съдържанието на клона, където git би ги запазил ([#2](https://github.com/inline0/pitmaster/issues/2)). Запишете ги с commit или ги приберете със stash, преди да превключите.

### Проверка дали е слято

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

## Управление на тагове

### Лек таг

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

Лекият таг е просто ref, който сочи директно към commit.

### Анотиран таг

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

Анотираният таг създава обект на тага (създател, съобщение и цел) и ref, който сочи към него. Създателят е самоличността на текущия committer, определена по същия начин като автора на commit.

### Персонализиран създател на тага

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

## Директно обновяване на refs

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

## Директно записване на обекти

За по-специални случаи можете да записвате обекти, без да използвате високото ниво на API.

### Записване на 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!"
```

В хранилище SHA-256 подайте алгоритъма: `Blob::fromContent('Hello, world!', 'sha256')`.

### Записване на дърво

```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()` записва записите в зададения ред и низовете за режима така, както са подадени. Подайте ги в реда на дървото на git (по име, като директориите се сравняват все едно името им завършва на `/`) и записвайте режимите на директориите като `40000`: git съхранява режимите на дърветата без водещата нула, а `git fsck --strict` отхвърля `040000`.

### Записване на 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);
```

## Нулиране

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

## Възстановяване

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

## Избиране на commit и отмяна

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

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

И двете команди приемат всяка ревизия, която приема `resolve()`. Пълните хешове работят, съкратените — не.
