
# クローン、フェッチ、プッシュ

Pitmasterは、gitの基本的な3つのネットワーク操作、clone（初回ダウンロード）、fetch（差分ダウンロード）、push（アップロード）をサポートします。SSH URL（`ssh://...`、`user@host:path`）ではSSHトランスポートを使用し、その他のURLではスマートHTTPを使用します。スマートな通信に失敗すると、cloneとfetchはダムHTTPにフォールバックします（[プロトコル](https://pitmaster.inline0.dev/ja/docs/network/protocols)を参照）。

3つの操作はいずれもネットワークトランスポートを開始するため、`['processes' => false]`または`Pitmaster::processFreeOptions()`で開いたハンドルでは`RuntimeException`をスローします。

## クローン

```php
use Pitmaster\Pitmaster;

// public static function clone(string $url, string $path, ?int $depth = null, array $options = []): Repository
$repo = Pitmaster::clone('https://github.com/user/repo.git', '/path/to/destination');
$repo = Pitmaster::clone('https://github.com/user/repo.git', '/path/to/shallow', 1);
```

新しいリポジトリでは常にSHA-1を使用します。クローンに失敗し、実行前に宛先が存在していなかった場合は、作成途中のディレクトリを削除します。

### クローンの流れ

1. **初期化**：宛先パスに新しい空のリポジトリを作成
2. **リモートを設定**：URLを`remote.origin.url`として保存し、フェッチ用のrefspecを設定
3. **refを検出**：`GET /info/refs?service=git-upload-pack`を実行してサーバー上の状態を確認
4. **ネゴシエーション**：すべてのリモートrefについて`want`行を送信（ハッシュで重複を排除）
5. **packを受信**：要求したすべてのオブジェクトを含むpackファイルをダウンロード
6. **packを書き込み**：packファイルを`.git/objects/pack/`に保存
7. **packにインデックスを作成**：高速検索用の`.idx`ファイルを生成
8. **refを設定**：リモート追跡ブランチ（`refs/remotes/origin/*`）とタグを作成
9. **HEADを設定**：デフォルトブランチをHEADの参照先に設定（サーバーの`symref=HEAD:...`機能から取得し、サーバーが何も送らない場合は`refs/heads/main`を使用）。`branch.<name>.remote`と`branch.<name>.merge`も設定
10. **チェックアウト**：そのブランチをチェックアウトし、作業ツリーとインデックスを書き込む

最後のチェックアウト中に発生したエラーは捕捉して無視されるため、クローン後の作業ツリーが空または不完全な場合があります（[#19](https://github.com/inline0/pitmaster/issues/19)）。

```php
$repo = Pitmaster::clone($url, $path);
$head = $repo->head();
echo $head->message;

$branches = $repo->branches();
$files = $repo->status();
```

### 作業ツリーの展開

packの受信後、Pitmasterはデフォルトブランチに対して`$repo->checkout()`を呼び出します。これによりHEADコミットのツリー内のすべてのファイルが作業ディレクトリに書き込まれ、ツリーエントリからインデックスが作成されるため、`status()`は作業ツリーに変更がないと報告します。

## フェッチ

```php
// public function fetch(string $remote = 'origin', ?int $depth = null): void
$repo->fetch();              // Fetch from 'origin'
$repo->fetch('upstream');    // Fetch from a named remote
$repo->fetch('origin', 1);   // Shallow fetch
```

### フェッチの流れ

1. **リモートURLを読み込む**：`.git/config`から取得（`remote.<name>.url`）
2. **refを検出**：すべてのリモートrefの現在の状態を取得
3. **want/haveをネゴシエート**：
   - Want：`remote.<name>.fetch`のrefspec（デフォルトは`+refs/heads/*:refs/remotes/<name>/*`）に一致し、オブジェクトがローカルに存在しないリモートref（深さを指定した場合はすべて）
   - Have：すべてのローカルrefの先端
4. **packを受信**：新しいオブジェクトのみをダウンロード
5. **packを書き込み**：`.git/objects/pack/`に保存し、インデックスを作成
6. **追跡refを更新**：一致した各リモートブランチの`refs/remotes/<remote>/<branch>`を更新し、通知されたすべてのタグrefを書き込む。タグオブジェクトは個別に要求しないため、新しい注釈付きタグがダウンロードされていないオブジェクトへのrefとして書き込まれる場合があります（[#24](https://github.com/inline0/pitmaster/issues/24)）

```php
// Fetch is incremental: only downloads what's new
$repo->fetch();

// After fetching, merge or rebase to incorporate changes (use the full ref name)
$result = $repo->merge('refs/remotes/origin/main');
```

`resolve()`は名前を`refs/heads/<name>`、`refs/tags/<name>`、名前そのものの順に検索するため、短縮形の`origin/main`は見つかりません。

Pitmasterは薄いpackを要求しますが、現時点ではインデックスを作成できません（[#16](https://github.com/inline0/pitmaster/issues/16)）。差分フェッチに対してサーバーが薄いpackを返すと、`fetch()`は`PackParseException`をスローします。

### 最新の状態

リモートrefが指すすべてのオブジェクトがすでにローカルに存在する場合、packは要求されず、refの検出後に`fetch()`は戻ります。この場合、すでに取得済みのコミットへリモートブランチが移動していても、追跡refは更新されません（[#23](https://github.com/inline0/pitmaster/issues/23)）。

## プッシュ

```php
$repo->push();                        // Push current branch to 'origin'
$repo->push('origin', 'main');        // Push a specific branch
$repo->push('upstream', 'feature');   // Push to a named remote
```

### プッシュの流れ

1. **リモートURLを読み込む**：`.git/config`から取得
2. **プッシュする内容を決定**：ローカルブランチをコミットハッシュに解決
3. **リモートrefを検出**：リモートブランチの現在の状態を取得し、古いハッシュを計算
4. **ファストフォワードを確認**：リモートブランチがローカルブランチの祖先でない場合、何も送信する前に`RuntimeException("Push rejected: non-fast-forward update to <branch>")`をスロー
5. **ref更新を送信**：`<old-hash> <new-hash> refs/heads/<branch>`
6. **packデータを送信**：リモートに必要なオブジェクトを含むpack
7. **結果を確認**：サーバーが各refの成功または失敗を応答

フックが無効でない限り、更新の送信前に`pre-push`フックが実行されます。

```php
// Push the current branch
$repo->push();

// Push a specific branch to a specific remote
$repo->push('origin', 'feature/login');
```

### プッシュの要件

- 現在のブランチがローカルに存在すること
- リモートにURLが設定されていること
- リモートがプッシュを受け入れること（認証、権限、非ファストフォワードのポリシー）

### 強制プッシュ

Pitmasterはリモートのライブref通知から古いハッシュを取得し、`push()`はローカルでの非ファストフォワード更新を拒否します。リモートブランチを上書きするには、force-with-leaseを使用してください。

```php
// Lease defaults to refs/remotes/<remote>/<branch>; pass an ObjectId to set it explicitly
$repo->pushForceWithLease('origin', 'main');
$repo->pushForceWithLease('origin', 'main', $repo->resolve('refs/remotes/origin/main'));

// Update several branches in one atomic request
$repo->pushAtomic('origin', ['main', 'release']);

// Make the remote mirror local refs (also deletes remote refs that no longer exist locally)
$repo->pushMirror('origin');
```

## UploadPackClient（フェッチの内部処理）

```php
use Pitmaster\Protocol\UploadPackClient;
use Pitmaster\Protocol\SmartHttpClient;

$http = new SmartHttpClient();
$uploadPack = new UploadPackClient($http);

// Fetch with explicit wants and haves
$packData = $uploadPack->fetch($url, $wants, $haves);
```

want/haveのネゴシエーションでは、pkt-lineリクエストを構築します。

```
want <hash1> <capabilities>\n
want <hash2>\n
deepen <depth>\n        (only when a depth is given)
0000
have <hash3>\n
have <hash4>\n
done\n
```

サーバーは確認応答の後にpackデータを返します（サイドバンドでラップされる場合があります）。

## ReceivePackClient（プッシュの内部処理）

```php
use Pitmaster\Protocol\ReceivePackClient;
use Pitmaster\Protocol\SmartHttpClient;

$http = new SmartHttpClient();
$receivePack = new ReceivePackClient($http);

$receivePack->push($url, [
    [
        'old' => $oldId,      // Current remote hash (or zero hash for new branch)
        'new' => $localId,    // Local commit hash to push
        'ref' => 'refs/heads/main',
    ],
], $packData);
```

プッシュリクエストの形式：

```
<old-hash> <new-hash> refs/heads/main\0<capabilities>\n
0000
PACK<pack data>
```

## エラー処理

```php
use Pitmaster\Exceptions\ProtocolException;

try {
    $repo->fetch();
} catch (ProtocolException $e) {
    // Network unreachable, auth failed, server error, etc.
    echo $e->getMessage();
}

try {
    $repo->push();
} catch (\RuntimeException $e) {
    // Remote not configured, not on a branch, branch not found, non-fast-forward
    echo $e->getMessage();
}
```

`fetch()`は、通常の`RuntimeException`（不明なリモート、プロセスのないハンドル）または`PackParseException`（インデックスを作成できないpack）をスローすることもあります。`ProtocolException`と`PackParseException`はいずれも`\RuntimeException`を継承するため、`\RuntimeException`で捕捉すればすべてに対応できます。

## リモートの設定

リモートは`.git/config`に保存されます。

```ini
[remote "origin"]
    url = https://github.com/user/repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*
```

設定APIからアクセスします。

```php
$config = $repo->config();
$url = $config->get('remote.origin.url');
$config->set('remote.upstream.url', 'https://github.com/other/repo.git');
$config->set('remote.upstream.fetch', '+refs/heads/*:refs/remotes/upstream/*');

// set() changes the in-memory config only; write it to persist the change
$config->writeToFile($repo->commonGitDir() . '/config');
```

## プッシュ用packデータ

現在、Pitmasterはプッシュ対象のコミットから到達可能で、リモートの現在のrefからは到達できないオブジェクトを収集し、デルタ圧縮なしのベースオブジェクトとしてpackに書き込みます。これは機能上正しいものの、`git push`よりpackのサイズが大きくなります。pack書き込み時のデルタ圧縮は今後の最適化として予定されています。
