
# Запись в репозитории

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. **Создание дерева по индексу**: плоский список записей индекса преобразуется в иерархию деревьев (каталоги становятся объектами tree, файлы — ссылками на 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);
```

## Проверка с помощью fsck в git

Интеграционные проверки 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()`. Полные хеши подходят, сокращённые — нет.
