
# リポジトリへの書き込み

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'`を2番目の引数として渡すと、SHA-256リポジトリを作成できます：`Pitmaster::init($path, 'sha256')`。

デフォルト設定では`core.logallrefupdates`が設定されないため、Pitmasterが作成したリポジトリでは、そのキーを`true`に設定するまで reflog エントリが記録されません（[#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 が指すブランチ参照を、新しいコミットハッシュに更新します。

ハンドルを`['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`と照合します。同じ検証を手動でも行えます。

```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 をブランチを指すシンボリック参照に更新し、インデックスをブランチ先端のツリーに合わせてリセットして、作業ツリーを更新します。

2つのツリー間で異なるパスにローカル変更があると、`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);
```

## 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()`が受け付ける任意のリビジョンを指定できます。完全なハッシュは使えますが、省略形のハッシュは使えません。
