본문으로 건너뛰기

Git 객체

git 저장소의 모든 항목은 객체로 저장됩니다. 객체 유형은 네 가지입니다. 블롭(파일 콘텐츠), 트리(디렉터리 목록), 커밋(메타데이터가 포함된 스냅샷), 태그(주석이 달린 참조)입니다. 각 객체는 콘텐츠의 SHA-1 해시로 식별됩니다.

객체 유형

Blob

blob에는 메타데이터(파일 이름, 권한 등) 없이 원시 파일 콘텐츠가 저장됩니다.

PHP
use Pitmaster\Object\Blob;

// Create from content
$blob = Blob::fromContent('Hello, world!');
echo $blob->content;    // 'Hello, world!'
echo $blob->size();     // 13
echo $blob->id->hex;    // SHA-1 hash
echo $blob->type->value; // 'blob' (the property is ObjectType::Blob)

트리

tree는 디렉터리 목록입니다. 각 항목에는 blob 또는 하위 tree를 가리키는 모드, 이름, 해시가 있습니다.

PHP
use Pitmaster\Object\Tree;
use Pitmaster\Object\TreeEntry;

// Read a tree
$tree = $repo->readObject($treeHash);

foreach ($tree->entries as $entry) {
    echo "{$entry->mode} {$entry->name} {$entry->hash->hex}\n";
}

// Look up a specific entry
$entry = $tree->entry('README.md');

// Create a tree (entries already in git's sort order)
$entries = [
    new TreeEntry('100644', 'README.md', $blobId),
    new TreeEntry('100755', 'deploy.sh', $execBlobId),
    new TreeEntry('40000', 'src', $subtreeId),
];

$tree = Tree::fromEntries($entries);

Tree::fromEntries()은 주어진 순서대로 항목을 쓰고 모드 문자열도 입력된 그대로 사용하므로, 먼저 정렬하고 디렉터리 모드는 40000로 써야 합니다. 040000로 tree를 쓰거나 순서를 다르게 하면 git의 해시와 달라지며, git fsck --strict은 앞에 0을 채운 모드를 보고합니다.

TreeEntry

TreeEntry은 읽기 전용 값 객체입니다.

PHP
use Pitmaster\Object\TreeEntry;

$entry->mode;   // '100644', '100755', '40000', '120000', '160000'
$entry->name;   // Filename
$entry->hash;   // ObjectId

// Type checks
$entry->isBlob();        // regular or executable file (mode 10xxxx)
$entry->isExecutable();  // mode 100755
$entry->isTree();        // mode 40000 (also accepts 040000)
$entry->isSymlink();     // mode 120000
$entry->isGitlink();     // mode 160000 (submodule)

모드 값:

모드 의미
100644 일반 파일
100755 실행 파일
40000 디렉터리(tree); git ls-tree에서는 040000으로 표시
120000 심볼릭 링크
160000 Gitlink(하위 모듈)

tree 항목은 이름순으로 정렬되며, 비교할 때 디렉터리 이름은 끝에 /이 붙은 것으로 간주합니다.

커밋

commit은 tree를 기록과 메타데이터에 연결합니다.

PHP
use Pitmaster\Object\Commit;

$commit = $repo->head();

$commit->id;         // ObjectId
$commit->tree;       // ObjectId of the tree
$commit->parents;    // array of ObjectId (zero for root, one for normal, two+ for merge)
$commit->author;     // 'Name <email> timestamp timezone'
$commit->committer;  // 'Name <email> timestamp timezone'
$commit->message;    // Full commit message

$commit->isMerge();  // true if two or more parents

커밋 콘텐츠 만들기

PHP
$content = Commit::buildContent(
    tree: $treeId,
    parents: [$parentId],
    author: 'Jane <jane@example.com> 1700000000 +0000',
    committer: 'Jane <jane@example.com> 1700000000 +0000',
    message: "Add feature\n",
);

원시 commit 형식:

코드
tree <hex hash>\n
parent <hex hash>\n       (zero or more)
author <name> <<email>> <timestamp> <tz>\n
committer <name> <<email>> <timestamp> <tz>\n
\n
<message>

태그

주석이 달린 tag는 메타데이터가 있는 객체입니다.

PHP
use Pitmaster\Object\Tag;

$tag = $repo->readObject($tagHash);

$tag->object;        // ObjectId of the target
$tag->objectType;    // ObjectType enum (ObjectType::Commit, ...)
$tag->name;          // Tag name
$tag->tagger;        // 'Name <email> timestamp timezone'
$tag->message;       // Tag message

원시 tag 형식:

코드
object <hex hash>\n
type commit\n
tag <name>\n
tagger <name> <<email>> <timestamp> <tz>\n
\n
<message>

ObjectId

ObjectId은 SHA-1(또는 SHA-256) 해시를 나타내는 읽기 전용 값 객체입니다.

PHP
use Pitmaster\Object\ObjectId;
use Pitmaster\Object\ObjectType;

// From hex string
$id = ObjectId::fromHex('a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2');

// From binary (20 bytes for SHA-1, 32 for SHA-256)
$id = ObjectId::fromBinary($rawBytes);

// Compute from content
$id = ObjectId::compute(ObjectType::Blob, 'Hello, world!');
$id = ObjectId::compute(ObjectType::Blob, 'Hello, world!', 'sha256');

// Zero hash
$id = ObjectId::zero();
$id = ObjectId::zero('sha256');

// Properties
$id->hex;      // 40-character hex string (64 for SHA-256)
$id->binary;   // 20-byte raw binary (32 for SHA-256)
$id->algo;     // 'sha1' or 'sha256'
$id->isSha256();
$id->isZero();

// Comparison
$id->equals($otherId);

해시는 압축되지 않은 데이터, 즉 같은 바이트의 sha1("{type} {size}{content}") 또는 SHA-256을 기준으로 계산됩니다. fromHex()와 fromBinary()은 길이를 기준으로 알고리즘을 선택합니다.

ObjectType 열거형

PHP
use Pitmaster\Object\ObjectType;

ObjectType::Blob;    // 'blob'
ObjectType::Tree;    // 'tree'
ObjectType::Commit;  // 'commit'
ObjectType::Tag;     // 'tag'

Loose 객체 형식

Loose 객체는 .git/objects/에 개별 파일로 저장되며, 해시의 처음 두 16진수 문자를 기준으로 구성됩니다.

코드
.git/objects/a1/b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

파일 콘텐츠는 zlib으로 압축됩니다. 압축을 푼 콘텐츠는 다음과 같습니다.

코드
<type> <decimal size>\0<raw content>

예를 들어 "Hello"가 들어 있는 blob은 다음과 같이 저장됩니다.

코드
zlib_encode("blob 5\0Hello")

Loose 객체 읽기

PHP
use Pitmaster\Storage\LooseObjectStore;

$store = new LooseObjectStore('/path/to/.git/objects');
$object = $store->read($objectId);

LooseObjectStore은 다음 작업을 수행합니다.

  1. 해시에서 파일 경로를 계산합니다.
  2. 파일을 읽고 zlib 압축을 풉니다.
  3. 헤더를 파싱해 유형과 크기를 추출합니다.
  4. 적절한 파서(Blob, Tree, Commit, Tag)에 처리를 위임합니다.

Loose 객체 쓰기

PHP
$store->write($object);

쓰기 과정:

  1. 직렬화: "{type} {size}\0{content}"
  2. 객체를 만들 때 계산한 객체의 id을 사용합니다(읽을 때 해시를 확인합니다).
  3. 압축: zlib_encode(serialized)
  4. .git/objects/{hash[0..1]}/{rest of hash}에 씁니다.
  5. 원자적으로 씁니다(임시 파일에 쓴 다음 이름을 바꿉니다).

ObjectSerializer

ObjectSerializer은 객체 와이어 형식의 인코딩과 디코딩을 처리합니다.

PHP
use Pitmaster\Storage\ObjectSerializer;

// Encode
$raw = ObjectSerializer::encodeRaw($object);
// "blob 5\0Hello"
$compressed = ObjectSerializer::encode($object);
// zlib-compressed form, as stored in a loose object file

// Decode
$object = ObjectSerializer::decodeRaw($raw);
$object = ObjectSerializer::decode($compressed, $expectedHex); // throws CorruptObjectException on a hash mismatch

ObjectDatabase

ObjectDatabase은 먼저 loose 객체를 검색한 뒤 pack 파일을 검색하는 복합 저장소입니다.

PHP
use Pitmaster\Storage\ObjectDatabase;

$db = new ObjectDatabase('/path/to/.git/objects');

$object = $db->read($objectId);    // Search loose, then packs
$db->write($object);               // Always writes to loose
$exists = $db->exists($objectId);  // Check loose, then packs
$all = $db->listAll();             // All hashes from loose + packs

loose 저장소에 쓰는 것은 의도된 동작입니다. git gc이 pack 파일로 다시 패킹하도록 하세요.

SHA-256 지원

Pitmaster은 ObjectId을 통해 해시 알고리즘을 추상화합니다. 기본값은 SHA-1입니다. SHA-256은 저장소를 만들 때 저장소별로 선택됩니다.

PHP
$repo = Pitmaster::init('/path/to/project', 'sha256');
셸
./vendor/bin/pitmaster init --object-format=sha256

이렇게 하면 extensions.objectformat = sha256이 설정 파일에 기록됩니다. 나중에 연 저장소는 해당 항목에서 형식을 읽고, 이후 모든 객체, tree, 인덱스, ref 작업에서 64자 16진수 문자열과 32바이트 바이너리 해시를 사용합니다. 알고리즘을 위한 전역 전환이나 상수는 없습니다.

객체를 직접 만들 때 객체 클래스에 알고리즘을 인수로 전달합니다: Blob::fromContent($content, 'sha256'), Tree::fromEntries($entries, 'sha256'), ObjectId::compute($type, $content, 'sha256'). Pitmaster::clone()은 SHA-1 저장소를 만듭니다.

Tree 항목의 바이너리 형식

tree 객체 콘텐츠에서 각 항목은 다음과 같이 인코딩됩니다.

코드
<octal mode as ASCII> <space> <filename> \0 <binary hash: 20 bytes SHA-1, 32 bytes SHA-256>

참고: commit 및 tag 객체의 16진수 해시와 달리, 해시는 16진수가 아닌 원시 바이너리입니다.

디렉터리 이름 뒤에 /이 붙은 것으로 간주해 비교한 순서대로 항목을 정렬합니다. tree의 해시가 git의 계산값과 일치하려면 이 정렬 순서가 정확해야 합니다.