Skip to content

Commit 01c631d

Browse files
authored
fix(branch-tag-reset): close review gaps (#334)
* fix(branch-tag-reset): close review gaps Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(branch-reset-docs): address review follow-ups Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(branch-remote-review): address follow-up review gaps Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(head-review): propagate fallible branch and cwd handling Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(branch): surface show-current head resolution failures Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(branch-status-show-ref): address follow-up review findings Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(local-client-branch-clone): address follow-up review findings Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(cli-tag-show-ref): address follow-up review findings Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(errors): preserve rollback and tag context Signed-off-by: Eli Ma <eli@patch.sh> * test(command): skip permission injection under root Signed-off-by: Eli Ma <eli@patch.sh> * fix(reset): preserve warnings and revision error codes Signed-off-by: Eli Ma <eli@patch.sh> * fix(local-client-tag): address follow-up review findings Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(restore-reset): address follow-up review findings Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(head-util): harden head and commit-base resolution Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(head-status-log): address follow-up review findings Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(branch-storage): remove lossy branch lookup fallbacks Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(head-show-merge): address follow-up review findings Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(log-util-storage): address follow-up review findings Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(ai-history): retry sqlite busy during ref updates Signed-off-by: Quanyi Ma <eli@patch.sh> * fix(ai-history): remove expect from retry path Signed-off-by: Quanyi Ma <eli@patch.sh> --------- Signed-off-by: Quanyi Ma <eli@patch.sh> Signed-off-by: Eli Ma <eli@patch.sh>
1 parent 2a15dc8 commit 01c631d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+5190
-1834
lines changed

docs/improvement/README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,11 @@
7373

7474
| 顺序 | 命令 | 当前状态 | 改进重点 |
7575
|------|------|--------|--------|
76-
| **9** | `switch` | 有 JSON + 确认消息 | `SwitchError` typed enum + 显式 `StableErrorCode``run_switch()` 返回 `Result<SwitchOutput, SwitchError>`;Levenshtein 模糊匹配;`--help` EXAMPLES(详见 [switch.md](switch.md)|
77-
| **9a** | `checkout`(兼容收口) | 依赖 `switch::ensure_clean_status()` | `switch` 联动:`err.message()` 字符串匹配改为 `SwitchError` 变体匹配;`--help` EXAMPLES**不是完整现代化**——JSON / `CheckoutError` / render split 仍留第六批(详见 [checkout.md](checkout.md)|
78-
| **10** | `reset` | 有确认消息,无 JSON | 输出 "HEAD is now at \<SHA\> \<msg\>";JSON 输出;错误码 |
79-
| **11** | `tag` | 有短标志 -l/-d/-m/-a | 补齐 JSON 输出;重复创建时 hint;退出码对齐 exit 1 |
80-
| **12** | `branch` | JSON | 补齐 StableErrorCode;退出码对齐(删除不存在分支 exit 1|
76+
| **9** | `switch` | ✅ 已落地 | 第二批主改造已落地;后续仅维护回归测试、文档同步与大仓库切换性能观察(详见 [switch.md](switch.md)|
77+
| **9a** | `checkout`(兼容收口) | ✅ 第二批兼容收口已落地 | 已完成 `SwitchError` 变体匹配适配与 `--help` EXAMPLES**不是完整现代化**——`CheckoutError` / JSON / render split 仍留第六批(详见 [checkout.md](checkout.md)|
78+
| **10** | `reset` | ✅ 主改造已落地:已有确认消息、JSON/machine、显式 `StableErrorCode``ResetError`、warning 管线、`run_reset()` / `render_reset_output()` | 后续仅维护 rollback / warning / pathspec corruption 边界回归与文档示例(详见 [reset.md](reset.md) |
79+
| **11** | `tag` | ✅ 主改造已落地:已有 JSON/machine、显式 `StableErrorCode``TagError`、run/render 分层、重复创建 hint 与统一 human 确认消息 | 后续仅维护 lightweight tag 的 human / machine 双契约、边界回归与文档同步(详见 [tag.md](tag.md) |
80+
| **12** | `branch` | 主改造已落地:JSON 已覆盖 list/create/delete/rename/set-upstream/show-current,`BranchError` typed enum、run/render 分层、确认消息、fuzzy suggestion 与 `--help` EXAMPLES 已就绪 | 继续把旧调用点迁移到 `internal::branch::*_result` fallible API,减少 legacy best-effort 查询路径(详见 [branch.md](branch.md)|
8181

8282
**理由:** 这些命令改变仓库状态,必须告知用户发生了什么。`checkout` 的兼容收口随 `switch` 一起落地,因为 `switch``ensure_clean_status()` 签名变更强制要求 `checkout` 同步适配。
8383

@@ -180,8 +180,8 @@
180180
- [Commit 命令改进详细计划](commit.md) ✅ 已落地
181181
- [Push 命令改进详细计划](push.md) ✅ 已落地
182182
- [Pull 命令改进详细计划](pull.md) ✅ 已落地
183-
- [Switch 命令改进详细计划](switch.md)
184-
- [Checkout 命令改进详细计划(第二批兼容收口)](checkout.md)
183+
- [Switch 命令改进详细计划](switch.md) ✅ 已落地
184+
- [Checkout 命令改进详细计划(第二批兼容收口)](checkout.md) ✅ 已落地(完整现代化留第六批)
185185
- [Reset 命令改进详细计划](reset.md)
186186
- [Tag 命令改进详细计划](tag.md)
187187
- [Branch 命令改进详细计划](branch.md)

docs/improvement/branch.md

Lines changed: 128 additions & 92 deletions
Large diffs are not rendered by default.

docs/improvement/reset.md

Lines changed: 120 additions & 51 deletions
Large diffs are not rendered by default.

docs/improvement/tag.md

Lines changed: 99 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
## Tag 命令改进详细计划
22

3-
> 最后编写时间:2026-03-30
3+
> 最后编写时间:2026-04-01
44
55
同时落地 [Cross-Cutting Improvements A/B/F/G](README.md#全局层面改进贯穿所有命令)
66

7+
> 当前工作区实现已按本文范围落地一部分改动;以下内容改为记录已落地能力、剩余遗漏和后续收口项。
8+
79
### 已完成前置条件与当前代码状态
810

911
第一批全部 8 个命令的主改造已在当前代码库落地。`tag` 是第二批(状态变更确认命令)中管理版本标记的命令。
@@ -14,33 +16,41 @@
1416
- `OutputConfig` + `emit_json_data()` + `info_println!()` 输出框架已可用
1517
- `StableErrorCode` 体系已有 18 个错误码
1618
- `CliError` 支持 `.with_hint()``.with_stable_code()``.with_detail()`
17-
- `execute()` / `execute_safe(args, _output)` 双入口已存在(`tag.rs:42/51`
19+
- `execute()` / `execute_safe(args, output)` 双入口已存在
20+
- `run_tag()` + `TagOutput` 已实现 list / create / delete 的 JSON / machine 输出
1821
- `-l` / `-d` / `-m` / `-f` / `-n` 短标志已实现
19-
- `create_tag_safe()` 已有 `.with_hint()` 提供重复创建时的 hint(`tag.rs:87-96`
22+
- 重复创建时的 hint 已在 `map_create_tag_error()` `CliError` 映射中保留
2023
- `render_tags()` 支持 `-n` 控制注释行数显示
2124
- `internal::tag` 模块提供底层 tag API
22-
- 内部 tag API 已返回 `LBR-CONFLICT-002` 错误码(从 `tag_test.rs:157` 验证)
25+
- create / delete / find major error path 已在命令层映射显式 `StableErrorCode`
26+
- quiet delete、malformed ref delete 和 JSON schema 已有回归测试覆盖
27+
28+
**基于当前代码的 Review 结论(已改进部分 vs 仍需改进部分):**
29+
30+
已改进(当前代码已具备):
2331

24-
**基于当前代码的 Review 结论(tag 仍需改进的部分):**
32+
- **结构化输出已落地**`run_tag()` + `TagOutput` 已覆盖 list / create / delete 三类操作,`--json` / `--machine` 可直接使用
33+
- **主要命令层错误已带显式 `StableErrorCode`**:重复创建、HEAD unborn、tag not found、delete I/O 失败、repo read failure 等路径已有稳定错误码
34+
- **`TagError` typed enum 已落地**:create / list / delete / load 路径已统一收口到命令层 typed error
35+
- **统一 `run_tag()` / `render_tag_output()` 分层已落地**:human / JSON / machine 已走同一执行层和渲染层
36+
- **重复创建 hint 已落地**`map_create_tag_error()` 已保留删除旧 tag 或更换 tag 名的提示
37+
- **human 成功反馈已统一**:lightweight / annotated create 与 delete 都已输出单行确认消息
38+
- **`--help` EXAMPLES 已落地**
39+
- **quiet / malformed ref delete 回归已覆盖**:当前测试已覆盖 quiet delete、删除损坏 tag ref、JSON delete `hash = null` 等边界
2540

26-
- **零 JSON / machine 输出**`OutputConfig` 参数标记为 `_output` 完全未使用(`tag.rs:51`
27-
- **`StableErrorCode` 在命令层**:虽然内部 tag API 返回 `LBR-CONFLICT-002`,但命令层的 `CliError::fatal()` 无显式错误码
28-
- **`TagError` typed enum**:错误散落在 `execute_safe()``create_tag_safe()``delete_tag_safe()``show_tag_safe()`
29-
- **退出码不对齐**:重复创建时退出码应为明确的非零值(当前通过 `CliError::fatal()` 返回 128,但无 stable code)
30-
- **删除不存在 tag 时退出码不对齐**:应返回 exit `1``129`
31-
- **测试注释有全角括号**`tag_test.rs` 中有 `(lightweight tag)` 等全角括号应改为半角
41+
仍需改进:
42+
43+
- **legacy 说明尚未完全清理**:本文后文仍有少量历史设计叙述,需要继续收口为“现状 + follow-up”格式
44+
- **human / JSON 双契约需持续维护**:lightweight tag 当前保持 `message: null` 的 machine 契约,同时 human `-n` 列表仍显示 commit message;后续需要继续用回归测试锁住这一行为
3245

3346
### 目标与非目标
3447

35-
**本批目标:**
36-
- 引入 `TagError` typed error enum,覆盖 tag 层面的错误场景
37-
- 所有 `TagError → CliError` 映射使用显式 `StableErrorCode`
38-
- 拆分执行层与渲染层:新增 `run_tag(args) -> Result<TagOutput, TagError>` 纯执行入口
39-
- 实现 JSON 输出(tag 操作结果 + tag 列表结构化)
40-
- 重复创建时保留 hint(已有)并补齐 `StableErrorCode`
41-
- 删除不存在 tag 时返回 exit `129` + hint
42-
- 修复测试注释中的全角括号
43-
- 补齐 `--help` EXAMPLES 段
48+
**已完成目标:**
49+
- `TagError` typed error enum、显式 `StableErrorCode`、统一 `run_tag()` / `render_tag_output()` 分层、human 成功确认消息、create 失败来源结构化映射和 `--help` EXAMPLES 已落地
50+
51+
**后续收口目标:**
52+
- 继续维护 lightweight tag 的 human / machine 双契约和边界回归测试
53+
- 持续清理本文残留的历史设计说明,使计划文档完全反映当前实现
4454

4555
**本批非目标:**
4656
- **不重写 `internal::tag` 底层业务语义**。允许做类型收紧和错误建模调整(例如 `create()` 返回 `CreateTagError`),但不改变 tag 创建/删除/查询的语义行为
@@ -50,10 +60,11 @@
5060

5161
### 设计原则
5262

53-
1. **执行路径与渲染职责拆分**`execute_safe()` 根据 `OutputConfig` 分流 human / JSON 路径,JSON 路径返回结构化 `TagOutput`
63+
1. **执行层与渲染层拆分**`execute_safe()` 调用 `run_tag()` 收集结构化 `TagOutput` 结果,再根据 `OutputConfig` 通过 `render_tag_output()` 渲染 human / JSON / machine,消除当前 human 路径与 JSON 路径分治的架构
5464
2. **JSON 覆盖 list、create、delete 三种操作**:通过 `action` 字段区分
5565
3. **错误码显式映射**:每个 `TagError` 变体都有确定的 `StableErrorCode`
5666
4. **保留现有 hint**:重复创建时的 hint 保持一致
67+
5. **typed enum 自身携带错误分类信息**:不能依赖 `TagError` 变体外的来源注释再决定 `StableErrorCode`
5768

5869
### 特性 1:TagError typed error enum
5970

@@ -74,8 +85,20 @@ pub enum TagError {
7485
#[error("tag name is required")]
7586
MissingName,
7687

77-
#[error("failed to create tag '{name}': {detail}")]
78-
CreateFailed { name: String, detail: String },
88+
#[error("cannot create tag: HEAD does not point to a commit")]
89+
HeadUnborn,
90+
91+
#[error("failed to read existing tags before creating '{name}': {detail}")]
92+
CheckExistingFailed { name: String, detail: String },
93+
94+
#[error("failed to serialize annotated tag object: {0}")]
95+
SerializeAnnotatedTag(String),
96+
97+
#[error("failed to store annotated tag object: {0}")]
98+
StoreObjectFailed(String),
99+
100+
#[error("failed to persist tag reference '{name}': {detail}")]
101+
PersistReferenceFailed { name: String, detail: String },
79102

80103
#[error("failed to delete tag '{name}': {detail}")]
81104
DeleteFailed { name: String, detail: String },
@@ -88,6 +111,8 @@ pub enum TagError {
88111
}
89112
```
90113

114+
> **`internal::tag::CreateTagError` 的关系**`CreateTagError` 是底层业务模块定义的错误类型(含 `AlreadyExists``HeadUnborn``CheckExisting``SerializeTag``StoreObject``PersistReference`)。`TagError` 是命令层 typed enum,当前代码通过 `map_create_tag_error()` 完成收口映射:`CheckExisting``CheckExistingFailed``SerializeTag``SerializeAnnotatedTag``StoreObject``StoreObjectFailed``PersistReference``PersistReferenceFailed`
115+
91116
**`TagError → CliError` 显式映射:**
92117

93118
| TagError 变体 | StableErrorCode | 退出码 | hint |
@@ -96,64 +121,67 @@ pub enum TagError {
96121
| `AlreadyExists` | `ConflictOperationBlocked` | 128 | `delete it first with 'libra tag -d {name}'` + `or choose a different tag name` |
97122
| `NotFound` | `CliInvalidTarget` | 129 | `use 'libra tag -l' to list available tags` |
98123
| `MissingName` | `CliInvalidArguments` | 129 | `provide a tag name` |
99-
| `CreateFailed` | `IoWriteFailed` | 128 ||
124+
| `HeadUnborn` | `RepoStateInvalid` | 128 | `create a commit first before tagging HEAD` |
125+
| `CheckExistingFailed` | `RepoCorrupt` | 128 ||
126+
| `SerializeAnnotatedTag` | `InternalInvariant` | 128 | 附带 Issues URL |
127+
| `StoreObjectFailed` | `IoWriteFailed` | 128 ||
128+
| `PersistReferenceFailed` | `IoWriteFailed` | 128 ||
100129
| `DeleteFailed` | `IoWriteFailed` | 128 ||
101130
| `LoadFailed` | `RepoCorrupt` | 128 ||
102-
| `ListFailed` | `IoReadFailed` | 128 ||
131+
| `ListFailed` | `RepoCorrupt` | 128 ||
132+
133+
**与当前代码中 inline 错误的对应关系:**
134+
135+
| 当前代码位置 | 当前 inline 错误 | 对应 TagError 变体 |
136+
|-------------|-----------------|---------------------|
137+
| `execute_safe:75` | `validate_named_tag_action()` | `MissingName`(delete/force 缺少 tag 名) |
138+
| `execute_safe:89` | `CliError::fatal(e.to_string())` render_tags 失败 | `ListFailed` |
139+
| `create_tag_safe:148-151` | `map_create_tag_error()``AlreadyExists` | `AlreadyExists` |
140+
| `map_create_tag_error:162-165` | `CreateTagError::HeadUnborn` | `HeadUnborn` |
141+
| `map_create_tag_error:167-171` | `CreateTagError::CheckExisting` | `CheckExistingFailed` |
142+
| `map_create_tag_error:172-175` | `CreateTagError::SerializeTag` | `SerializeAnnotatedTag` |
143+
| `map_create_tag_error:176-178` | `CreateTagError::StoreObject` | `StoreObjectFailed` |
144+
| `map_create_tag_error:180-184` | `CreateTagError::PersistReference` | `PersistReferenceFailed` |
145+
| `delete_tag_safe:242-246` | `tag::delete().map_err(...)` | `DeleteFailed` |
146+
| `lookup_tag:357-365` | `Ok(None)` tag not found | `NotFound` |
147+
| `lookup_tag:357-365` | `Err(e)` repo corrupt | `LoadFailed` |
148+
| `collect_tags:346-350` | `tag::list().map_err(...)` | `ListFailed` |
149+
| `lookup_tag:332-334` | `Ok(None)` tag not found | `NotFound` |
150+
| `lookup_tag:335-337` | `Err(e)` repo corrupt | `LoadFailed` |
103151

104152
### 特性 2:执行层与渲染层拆分
105153

106-
**方案**
154+
**已落地部分(保持不变)** `TagOutput` enum(含 `List`/`Create`/`Delete` 三变体)、`TagListEntry` 结构体均已存在于 `tag.rs:41-63`,JSON schema 已稳定。
107155

108-
```rust
109-
#[derive(Debug, Clone, Serialize)]
110-
#[serde(tag = "action")]
111-
pub enum TagOutput {
112-
#[serde(rename = "list")]
113-
List(TagListOutput),
114-
#[serde(rename = "create")]
115-
Create(TagCreateOutput),
116-
#[serde(rename = "delete")]
117-
Delete(TagDeleteOutput),
118-
}
156+
**本批变更:统一 `run_tag()` / `render_tag_output()` 分层**
119157

120-
#[derive(Debug, Clone, Serialize)]
121-
pub struct TagListOutput {
122-
pub tags: Vec<TagListEntry>,
123-
}
158+
当前架构已经统一:`execute_safe()` 调用 `run_tag()` 收集结构化结果,再由 `render_tag_output()` 统一渲染 human / JSON / machine。list / create / delete 三类路径已经合流。
124159

125-
#[derive(Debug, Clone, Serialize)]
126-
pub struct TagListEntry {
127-
pub name: String,
128-
pub hash: String,
129-
/// "lightweight" or "annotated"
130-
pub tag_type: String,
131-
/// Annotation message (first N lines, None for lightweight)
132-
pub message: Option<String>,
133-
}
160+
目标架构:
134161

135-
#[derive(Debug, Clone, Serialize)]
136-
pub struct TagCreateOutput {
137-
pub name: String,
138-
pub hash: String,
139-
pub tag_type: String,
140-
pub message: Option<String>,
141-
}
162+
```rust
163+
/// 纯执行入口——收集结构化结果,不输出
164+
async fn run_tag(args: &TagArgs) -> Result<TagOutput, TagError>
142165

143-
#[derive(Debug, Clone, Serialize)]
144-
pub struct TagDeleteOutput {
145-
pub name: String,
146-
pub hash: Option<String>,
166+
/// 渲染层——根据 OutputConfig 决定 human/JSON/machine/quiet 输出
167+
fn render_tag_output(result: &TagOutput, output: &OutputConfig) -> CliResult<()>
168+
169+
/// execute_safe 调用链
170+
pub async fn execute_safe(args: TagArgs, output: &OutputConfig) -> CliResult<()> {
171+
let result = run_tag(&args).await.map_err(CliError::from)?;
172+
render_tag_output(&result, output)
147173
}
148174
```
149175

176+
历史上的 `run_tag_json()` 已合并入 `run_tag()``render_tags()` 当前主要保留给测试和辅助调用,实际 CLI human 列表渲染已在 `render_tag_output()` 中统一处理。
177+
150178
**渲染规则:**
151179

152180
| 模式 | stdout | stderr |
153181
|------|--------|--------|
154182
| human list | tag 名称列表(可选 `-n` 注释行数) ||
155-
| human create | 保留现有创建路径输出(lightweight create 后展示 tag/commit 信息;annotated create 无额外确认消息) ||
156-
| human delete | 确认消息(如 `Deleted tag 'v1.0' (was abc1234)` ||
183+
| human create | 统一确认消息:`Created lightweight tag 'v1.0' at abc1234``Created annotated tag 'v1.0' at abc1234` ||
184+
| human delete | 确认消息`Deleted tag 'v1.0' (was abc1234)`;target 丢失时退化为 `Deleted tag 'v1.0'` ||
157185
| human + `--quiet` |||
158186
| `--json` / `--machine` | JSON envelope(含 `action` 字段区分操作类型) ||
159187

@@ -227,10 +255,10 @@ When deleting malformed refs that have no stored target, `hash` is `null`.
227255

228256
| ID | 改进 | tag 中的具体落地 |
229257
|----|------|-----------------|
230-
| **A** | 退出码 `0/128/129` | 参数错误(缺少 tag 名、不存在的 tag 名)→ exit `129`;运行时错误(重复创建、I/O 失败)→ exit `128`;成功 → exit `0` |
258+
| **A** | 退出码 `0/128/129` | 参数错误(缺少 tag 名、不存在的 tag 名)→ exit `129`;运行时错误(重复创建、HEAD unborn、I/O 失败)→ exit `128`;成功 → exit `0` |
231259
| **B** | `--help` EXAMPLES | 见下方 EXAMPLES 段 |
232260
| **F** | 拼写纠错 | **不适用**——tag 名是用户自定义值,无 enum 可做 fuzzy match |
233-
| **G** | Issues URL | 仅在 `LoadFailed` / `ListFailed` 错误时输出 Issues URL |
261+
| **G** | Issues URL | 与 switch 保持一致——仅在映射为 `InternalInvariant` 的内部不变式错误时输出。当前仅 `SerializeAnnotatedTag` 属于此类;`RepoCorrupt`/`IoWriteFailed` 是数据或 I/O 问题,不附带 Issues URL |
234262

235263
### `--help` EXAMPLES 段
236264

@@ -252,9 +280,11 @@ EXAMPLES:
252280

253281
- **(已有)** 重复 tag 错误码、basic creation、annotated tag、force tag、list、delete、annotation lines
254282
- **(新增)`TagError` 变体覆盖**
255-
- `NotFound`:删除不存在 tag 返回 exit `129`
256-
- `MissingName`:无 tag 名返回 exit `129`
283+
- `NotFound`:删除不存在 tag 返回 exit `129` + `LBR-CLI-003`
284+
- `MissingName`:无 tag 名返回 exit `129` + `LBR-CLI-002`
285+
- `HeadUnborn`:空仓库创建 tag 返回 exit `128` + `LBR-REPO-003`
257286
- **(新增)quiet / delete 输出约束**`--quiet tag -d` 不应污染 stdout;human delete 保持确认消息
287+
- **(新增)human create 输出统一**:lightweight / annotated create 均输出单行确认消息,不再依赖 `show_tag_safe()` 打印详情
258288
- **(新增)force 失败路径回归**`-f` 遇到对象存储失败时必须保留原有 ref,不得丢 tag
259289
- **(修复)全角括号**:将 `(lightweight tag)` 等改为 `(lightweight tag)`
260290

@@ -280,5 +310,5 @@ EXAMPLES:
280310

281311
| 文件 | 改动类型 | 说明 |
282312
|------|---------|------|
283-
| `src/command/tag.rs` | **重构** | 新增 `TagError` typed enum;新增 `TagOutput` / `TagListOutput` / `TagCreateOutput` / `TagDeleteOutput` 结构体;命令层 `TagError → CliError` 显式 `StableErrorCode` 映射;JSON 输出;quiet/delete 输出约束;补齐 `--help` EXAMPLES |
284-
| `tests/command/tag_test.rs` | **扩展** | 新增 `TagError` 变体覆盖、JSON schema 回归、force 失败路径保护、修复全角括号 |
313+
| `src/command/tag.rs` | **维护** | 保持已落地的 `TagOutput` / `TagError` / `run_tag()` / `render_tag_output()` / create hint / human 确认消息 / `--help` EXAMPLES 不回退;后续仅维护双契约与边界回归 |
314+
| `tests/command/tag_test.rs` | **维护** | 在现有 JSON / quiet / malformed ref delete / lightweight-human-vs-machine 契约回归基础上,继续维护 `TagError` 变体覆盖与成功反馈一致性校验 |

src/cli.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,9 @@ pub async fn parse_async(args: Option<&[&str]>) -> CliResult<()> {
581581
_ => return Err(classify_parse_error(&argv, &err)),
582582
},
583583
};
584+
if let Commands::Tag(tag_args) = &args.command {
585+
command::tag::validate_cli_args(tag_args)?;
586+
}
584587
match &args.command {
585588
Commands::Init(_) | Commands::Clone(_) => {}
586589
// Config global/system scopes don't require a repository

0 commit comments

Comments
 (0)