
# CLI 参考手册

merql 自带一个 CLI，可直接从终端捕获快照、计算差异并运行合并。Composer 会将其安装为 `vendor/bin/merql`；在 merql 仓库的检出目录中，它是 `bin/merql`。

由于 merql 0.4.1，`vendor/bin/merql` 可在安装依赖后正常运行。早期版本只会在软件包自己的 `vendor/` 目录中查找 Composer 自动加载器，因此 merql 作为依赖安装时会运行失败。

## 快速开始

```bash
# Capture a baseline from your database.
export MERQL_DB_DSN="sqlite:/path/to/db.sqlite"
vendor/bin/merql snapshot base

# Make some changes, then capture again.
vendor/bin/merql snapshot current

# See what changed.
vendor/bin/merql diff base current
```

三方合并需要从两个相对于基准发生分歧的数据库中捕获 ours 和 theirs。为每个快照将 `MERQL_DB_DSN` 指向正确的数据库：

```bash
# Base: live before the sandbox was copied from it.
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot base

# ... copy live.sqlite to sandbox.sqlite, then both databases change ...

MERQL_DB_DSN="sqlite:/data/sandbox.sqlite" vendor/bin/merql snapshot ours
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot theirs

# Three-way merge with dry-run preview.
vendor/bin/merql merge base ours theirs --dry-run
```

依次从同一个数据库捕获全部三个快照，不能用于合并：theirs 会包含 ours 已做的更改，因此 ours 插入的每一行都会被报告为 `insert_insert` 冲突。

## 环境变量

CLI 从环境变量读取数据库连接详细信息。你可以使用 PDO DSN 字符串或单独的 MySQL 参数进行连接。

### 基于 DSN 的连接

对于基于 PDO 的连接，请使用 `MERQL_DB_DSN`。这是针对 SQLite 和非标准配置的推荐方法。DSN 必须指向 MySQL 或 SQLite 数据库：CLI 无法为其他引擎注册驱动程序。

| 变量 | 说明 | 示例 |
|---|---|---|
| `MERQL_DB_DSN` | 完整 PDO DSN 字符串 | `sqlite:/path/to/db.sqlite` |
| `MERQL_DB_USER` | 用户名（使用 DSN 时可选） | `root` |
| `MERQL_DB_PASS` | 密码（使用 DSN 时可选） | `secret` |

```bash
# SQLite file.
export MERQL_DB_DSN="sqlite:/var/data/myapp.sqlite"

# MySQL via DSN.
export MERQL_DB_DSN="mysql:host=127.0.0.1;dbname=mydb;charset=utf8mb4"
export MERQL_DB_USER="root"
export MERQL_DB_PASS="secret"
```

使用基于文件的 SQLite 数据库。每个 CLI 命令都在独立进程中运行，因此 `sqlite::memory:` 总会打开一个新的空数据库，其快照中不包含任何表。

### MySQL 简写

未设置 `MERQL_DB_DSN` 时，CLI 会改用特定于 MySQL 的变量。

| 变量 | 默认值 | 说明 |
|---|---|---|
| `MERQL_DB_NAME` | （必填） | 数据库名称 |
| `MERQL_DB_HOST` | `127.0.0.1` | MySQL 主机 |
| `MERQL_DB_PORT` | `3306` | MySQL 端口 |
| `MERQL_DB_USER` | `root` | MySQL 用户名 |
| `MERQL_DB_PASS` | （空） | MySQL 密码 |
| `MERQL_DB_ADAPTER` | `pdo` | 设为 `mysqli` 以使用 mysqli 适配器 |

```bash
export MERQL_DB_NAME="wordpress"
export MERQL_DB_HOST="127.0.0.1"
export MERQL_DB_PORT="3306"
export MERQL_DB_USER="root"
export MERQL_DB_PASS="password"
```

## 命令

### `merql snapshot <name>`

捕获环境变量所指定数据库的当前状态，并将其保存为命名快照。这是唯一会连接数据库的命令。

```bash
vendor/bin/merql snapshot baseline
```

输出：

```
Snapshot 'baseline' saved
Tables: posts, comments, users, settings
```

快照默认以 JSON 文件的形式持久化到工作目录下的 `.merql/snapshots/` 中（参见 [快照存储](#snapshot-storage)）。名称只能包含字母数字字符、连字符和下划线。

典型工作流程：进行更改前捕获一个快照，然后在更改后再捕获一个。两个快照构成计算差异或执行合并的基础。

### `merql diff <base> <current>`

比较两个命名快照并显示差异摘要。

```bash
vendor/bin/merql diff baseline current
```

输出：

```
Changeset: baseline -> current
  Inserts: 3
  Updates: 7
  Deletes: 1
```

两个快照都必须已存在（此前通过 `merql snapshot` 捕获）。差异计算基于保存的快照文件，无需连接数据库。

### `merql merge <base> <ours> <theirs>`

对三个命名快照执行三方合并。

```bash
vendor/bin/merql merge base ours theirs
```

输出：

```
Merge: base + ours + theirs
  Operations: 12
  Conflicts:  0
  Clean:      yes
```

三个快照都必须已存在。合并基于保存的快照文件，无需连接数据库，也不会写入数据库：CLI 没有 apply 命令。使用 PHP API 应用合并结果。

CLI 在列级别进行合并。它不会对 TEXT 或 JSON 列执行单元格级合并，因此同一列中的两处不同编辑在此始终会产生冲突（[#7](https://github.com/inline0/merql/issues/7)）。如需单元格级合并，请在 PHP 中将 `ThreeWayMerge` 与 `CellMergeConfig` 配合使用。

#### `--dry-run`

添加 `--dry-run` 标志可预览合并将生成的 SQL 语句，而不执行任何操作。这些语句描述如何将基准状态转换为合并结果，因此包含双方的更改。

```bash
vendor/bin/merql merge base ours theirs --dry-run
```

输出：

```
Merge: base + ours + theirs
  Operations: 3
  Conflicts:  0
  Clean:      yes

Dry run SQL:
  INSERT INTO `posts` (`id`, `title`, `status`) VALUES ('10', 'New Post', 'draft');
  UPDATE `posts` SET `title` = 'Updated Title' WHERE `id` = '5';
  DELETE FROM `comments` WHERE `id` = '3';
```

## 开发命令

merql 仓库的检出目录中，`bin/` 下还有供 oracle 驱动测试套件使用的脚本。这些脚本不属于 Composer 软件包，因此仅安装依赖时只有 `vendor/bin/merql`。在仓库根目录中 `composer install` 后运行这些脚本。

| 命令 | 说明 |
|---|---|
| `./bin/oracle <name>` | 计算场景的预期合并结果 |
| `./bin/actual <name>` | 在场景上运行 merql 并捕获输出 |
| `./bin/compare <name>` | 对比 oracle 与实际输出 |
| `./bin/test-scenario <name>` | 完整流程：设置、oracle、实际结果、比较 |
| `./bin/test-regression` | 运行所有场景 |
| `./bin/test-regression --category clean` | 运行一个类别：`clean`、`conflict`、`identity`、`types`、`scale` 或 `edge` |
| `./bin/test-regression --fast` | 仅显示通过/失败，不生成报告 |
| `./bin/verify-compliance` | 完整合规报告 |
| `./bin/verify-all` | PHPStan + 代码风格 + PHPUnit + oracle 回归测试 |

`bin/test-regression` 也接受 `--jobs`，但目前会忽略该值并依次运行场景（[#5](https://github.com/inline0/merql/issues/5)）。遇到未知的 `--category` 时会打印 `No scenarios found` 并成功退出。

场景布局请参阅 [测试策略](https://merql.inline0.dev/zh/docs/advanced/testing)。

## 退出状态

`merql snapshot`、`diff` 和 `merge` 缺少参数时会以状态码 1 退出；`snapshot` 在未配置连接时也会如此。缺少快照会导致命令因未捕获的 `SnapshotException` 而结束，并返回非零状态码。遇到未知命令时会打印 `Unknown command`，但目前会以状态码 0 退出（[#3](https://github.com/inline0/merql/issues/3)）。

## 快照存储

快照存储在相对于工作目录的 `.merql/snapshots/` 中。每个快照都是一个名为 `<name>.json` 的独立 JSON 文件。所有命令都要从同一目录运行，才能找到彼此的快照。

CLI 没有用于指定其他位置的选项或环境变量（[#8](https://github.com/inline0/merql/issues/8)）。如需使用其他位置，请在运行前切换到不同的工作目录。在 PHP 中，捕获或加载快照前先设置目录：

```php
use Merql\Snapshot\SnapshotStore;

SnapshotStore::setDirectory('/var/data/merql-snapshots');
```

快照文件可移植。你可以在不同计算机之间复制它们，将它们提交到版本控制中，或分享以便协作调试。由 CLI 捕获的快照可以在 PHP 中使用 `SnapshotStore::load()` 加载，反过来也可以，只要两者使用相同目录即可。
