
# Запис у репозиторії

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

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

## Створення комітів

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

Процес створення коміту:

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

Навколо цих кроків виконуються перехоплювачі (`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>`, що містить хеш цільового коміту.

### Видалення гілки

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

### Перемикання гілок

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

Оновлює HEAD як символічне посилання на гілку, скидає індекс до стану дерева на вершині гілки та оновлює робоче дерево.

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

### Перевірка злиття

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

Легковаговий тег — це просто посилання безпосередньо на коміт.

### Анотований тег

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

Анотований тег створює об’єкт тегу (з автором тегу, повідомленням і цільовим об’єктом) та посилання на нього. Автором тегу є поточний автор коміту; його дані визначаються так само, як для автора коміту.

### Власний автор тегу

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

## Безпосереднє оновлення посилань

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

### Запис коміту

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

## Вибіркове перенесення коміту та скасування змін

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

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

Обидві команди приймають будь-яку ревізію, яку підтримує `resolve()`. Повні хеші працюють, скорочені — ні.
