
# マージ

Pitmasterは`git merge`のような3ウェイマージでブランチをマージします。早送りの可能性を検出し、マージベースを計算して、ツリーレベルとコンテンツレベルのマージを実行します。ファイルが競合した場合は、標準的な競合マーカーを生成します。

## 基本的なマージ

```php
$result = $repo->merge('feature/login');

if ($result->clean) {
    echo "Merged successfully: {$result->commitId->hex}\n";
} else {
    echo "Conflicts:\n";
    foreach ($result->conflictPaths as $path) {
        echo "  {$path}\n";
    }
}
```

## MergeResult

`MergeResult`クラスは読み取り専用の値オブジェクトです:

```php
use Pitmaster\Merge\MergeResult;

$result->clean;           // bool: true if merge completed without conflicts
$result->commitId;        // ?ObjectId: the merge commit (null if conflicts)
$result->conflictPaths;   // array: list of file paths with conflicts
$result->mergedContents;  // array: path => content with conflict markers (if conflicts)
```

競合によってマージが停止すると、競合状態がインデックスと作業ツリーに書き込まれます。ファイルを解決して`add()`し、`$repo->mergeContinue()`を呼び出すか、`$repo->mergeAbort()`を呼び出してマージ前の状態に戻します。

マージ前にローカルの変更をコミットするかstashしてください。マージでは結果から作業ツリーを再構築し、追跡対象ファイルの未コミット変更を上書きします（[#2](https://github.com/inline0/pitmaster/issues/2)）。

## 早送りマージ

現在のHEADが対象ブランチの祖先である場合、Pitmasterは早送りマージを実行します。マージコミットを作成せず、HEADを対象コミットへ移動するだけです。

```
Before:         After fast-forward:

A---B (main)    A---B---C---D (main)
     \
      C---D (feature)
```

```php
$result = $repo->merge('feature');
// $result->clean === true
// $result->commitId points to D
// No merge commit created
```

逆のケースはまだ検出されません。HEADがすでに含んでいるブランチをマージすると、「すでに最新です」と報告されず、ツリーに変更のない新しいマージコミットが作成されます（[#6](https://github.com/inline0/pitmaster/issues/6)）。

## 三者マージ

ブランチの履歴が分岐している場合、Pitmasterは3ウェイマージを実行します:

1. マージベース（最も近い共通祖先）を見つける
2. ベースから自分側、ベースから相手側への差分をツリーレベルで計算する
3. 両方のブランチで変更された各ファイルについて、コンテンツレベルのマージを実行する
4. 2つの親を持つマージコミットを作成する

```
Before:              After merge:

      C---D (feature)         C---D
     /                       /     \
A---B---E---F (main)   A---B---E---F---G (main)
```

## マージベース

マージベースは2つのコミットの最新の共通祖先です。Pitmasterはコミットグラフをたどってこれを見つけます。マージベースが複数ある場合（交差する履歴）、`merge()`が先にそれらをマージし、その結果をベースとして使用します。

```php
$base = $repo->mergeBase(
    $repo->resolve('main'),
    $repo->resolve('feature/login'),
);

if ($base !== null) {
    echo "Common ancestor: {$base->hex}\n";
}
```

`MergeBase`アルゴリズムに直接アクセスするには:

```php
use Pitmaster\Merge\MergeBase;

$finder = new MergeBase($repo->objectDatabase());
$base = $finder->find($commitA, $commitB);

// All merge bases (criss-cross histories can have more than one)
$bases = $finder->findAll($commitA, $commitB);

// Check ancestry
$isAncestor = $finder->isAncestor($oldCommit, $newCommit);
```

## ブランチのマージ確認

```php
if ($repo->isBranchMerged('feature/done')) {
    // All commits in feature/done are reachable from default branch
    $repo->deleteBranch('feature/done');
}

// Check against a specific target
if ($repo->isBranchMerged('feature/done', 'develop')) {
    // Merged into develop
}
```

## 競合マーカー

両方のブランチでファイルが変更され、変更箇所が重なると、Pitmasterは標準的な競合マーカーを生成します。`merge()`は各側に`HEAD`とマージ後のブランチ名を付けます:

```
<<<<<<< HEAD
    return $this->value + 1;
=======
    return $this->value * 2;
>>>>>>> feature
```

`ConflictMarker`クラスは2つのバージョンをマーカーで囲みます:

```php
use Pitmaster\Merge\ConflictMarker;

$marked = ConflictMarker::mark($oursContent, $theirsContent, 'HEAD', 'feature');

// diff3 style, with the base section
$marked = ConflictMarker::mark($oursContent, $theirsContent, 'HEAD', 'feature', $baseContent, 'base');
```

## コンテンツレベルの3ウェイマージ

`ThreeWayMerge`クラスはblobの内容（ファイルではなく）をマージします:

```php
use Pitmaster\Merge\ThreeWayMerge;

$result = ThreeWayMerge::merge($baseContent, $oursContent, $theirsContent, 'HEAD', 'feature');

$result['content'];   // merged text, with conflict markers if needed
$result['clean'];     // true when nothing conflicted
$result['conflicts']; // number of conflict regions
```

ベース、自分側、相手側の3つの文字列に、任意のラベルと競合スタイルを指定できます。競合スタイルは既定で`'merge'`、ベース部分を含める場合は`'diff3'`です。

## マージ戦略

### 再帰的マージ

既定の戦略（`$repo->merge($branch)`）で、マージベースが複数ある交差マージの履歴を処理します。CLIでは、`-s ort`と`-s recursive`も指定できます。

### Oursマージ

HEADのツリーを変更せずにマージコミットを記録します。変更を取り込まずにブランチの履歴を維持する場合に便利です。

```php
$result = $repo->merge('feature/old', 'ours');
```

### Octopusマージ

2つ以上のブランチを1つのマージコミットにまとめます。いずれかのブランチで競合が発生すると、マージ状態を書き込まず、コミットも作成せずに、未解決の結果を返します。

```php
$result = $repo->mergeOctopus(['feature/a', 'feature/b', 'feature/c']);
```

`Pitmaster\Merge`の`RecursiveMerge`、`OursMerge`、`OctopusMerge`クラスはコンテンツレベルのヘルパーです。`Repository`は上記の戦略を独自に実装しています。

## Rerere（記録済みの解決を再利用）

Pitmasterには、gitの`rr-cache`レイアウトで解決内容を保存する独立した`Rerere`クラスがあります:

```php
use Pitmaster\Merge\Rerere;

$rerere = new Rerere($repo->gitDir());

if ($rerere->isEnabled()) {                     // rerere.enabled = true in .git/config
    $rerere->record($conflicted, $resolved);    // remember a resolution
    $resolved = $rerere->resolve($conflicted);  // replay it, or null
}
```

`merge()`はこれを呼び出しません。解決内容の記録と再適用は、コードから呼び出した場合にのみ行われます。`isEnabled()`は`.git/config`内のリテラルテキスト`rerere.enabled = true`を探しますが、`git config rerere.enabled true`が書き込む`[rerere]`セクションは認識しません（[#20](https://github.com/inline0/pitmaster/issues/20)）。

## ツリーレベルのマージの流れ

マージ処理の全体:

1. `MergeBase::findAll()`が共通祖先を特定する
2. ベース、自分側、相手側のツリーをパスごとに比較する
3. 片方のブランチのみで変更されたファイルは、その変更を採用する
4. 両方のブランチで変更されたファイルについて:
   - 両方が同じ変更をした場合は、どちらを採用してもよい（内容が一致）
   - 変更内容が異なる場合は、コンテンツに対して`ThreeWayMerge::merge()`を実行する
5. すべてのマージがクリーンなら、マージ済みツリーを構築してマージコミットを作成する
6. 競合がある場合は、`clean = false`と競合パスを含む`MergeResult`を返す

## CLIの使い方

```bash
./vendor/bin/pitmaster merge feature/login
```

成功時の出力：
```
Merge successful.
```

競合時の出力:
```
CONFLICT in: src/File.php, src/Other.php
```
