Перейти к содержимому

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

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). В репозиториях, созданных с помощью 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. Вы можете выполнить такую проверку вручную:

Командная оболочка
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). Перед переключением создайте коммит или сохраните изменения в хранилище.

Проверка, выполнено ли слияние

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