본문으로 건너뛰기

저장소에 쓰기

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가 만든 저장소에서는 해당 키를 true로 설정할 때까지 reflog 항목이 기록되지 않습니다(#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가 가리키는 브랜치 참조를 새 커밋 해시로 갱신합니다.

핸들을 ['hooks' => false]로 열지 않았다면 이 단계 전후에 훅(pre-commit, prepare-commit-msg, commit-msg, post-commit)이 실행됩니다. 메시지가 없거나 트리가 HEAD와 동일하거나 인덱스에 병합되지 않은 경로가 있으면 commit()에서 오류가 발생합니다.

사용자 지정 작성자

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

git fsck로 검증

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

주석 태그는 태그 객체(태거, 메시지, 대상 포함)와 이를 가리키는 참조를 만듭니다. 태거는 커밋 작성자와 같은 방식으로 확인되는 현재 커미터 ID입니다.

사용자 지정 태거

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은 앞의 0을 제외한 트리 모드를 저장하며 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);

Cherry-pick 및 revert

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

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

둘 다 resolve()가 허용하는 모든 리비전을 받습니다. 전체 해시는 사용할 수 있지만 축약 해시는 사용할 수 없습니다.