fix(onboarding/parity): 混合文件预警收窄空白归一、失败时不丢预警、测住改写判断(P10 审查修复)
P10 审查核实的三处缺陷: - 块外比较原先逐行 trim_end 并丢弃全部空行,项目在块外删空行(Markdown 两段 并成一段)或加行尾空格(硬换行)时判为无改动,升级后不预警却产生混合文件。 改为 CRLF 统一为 LF、托管块原位换成占位符,只把文末占位符连同其前空白按 trim_end 归一(正好抵消炼境追加托管块时的 trim_end + 空行);文中托管块两侧 的空行与行尾空格照常比较——炼境只原位替换文中的块,不碰两侧。 - dispatch_templates_with_mixed_file_check 先 `then()?` 后标注:buff 先同步蓝图 规则改写了 AGENTS.md/CLAUDE.md、再写 DB 失败时,预警随 Err 一起丢失。改为 无论成败先标注,失败时把混合文件名并入错误信息;无混合文件则原样返回错误。 - 「块外有未提交改动但托管块已是最新、本次不改写」组合没有测试,删掉 annotate 的改写判断测试照样全绿。主场景测试补情形三。 测试:新增 outside_managed_whitespace_edits_count_as_changes、 mixed_file_warning_survives_failure_after_write,主场景补情形三;纯函数测试的 期望值随归一化收窄更新("# 标题\n项目内容" → "# 标题\n\n项目内容",保留「之前 追加的块尚未提交、只多出块前空行」不算改动的断言)。变异自检:三处修复逐一回退, 对应测试各自失败,已还原。cargo test --locked:lib 272 passed / 5 ignored,0 failed; pnpm typecheck 通过。契约文档 project-context.yaml 与蓝图 P10 卡同步。 Rules-Applied: R07,R12 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
13aa3a1a8c
commit
c9b951dd30
@ -179,6 +179,7 @@ skill 只是五个接入面之一,兼容层覆盖全部五面。
|
||||
- depends: P8
|
||||
- acceptance: "agent_verify: [cargo test] 目标文件在 git 中有托管块之外的未提交改动时,apply_onboarding_pack 的预览与结果对该文件给出 warning(含文件名与「块外有未提交改动,升级后将成为混合文件」提示),默认仍只改托管块、不改块外内容;无未提交改动或非 git 仓库时无此提示。"
|
||||
- 为什么:2026-10-01 enterprise 的 docs/ai-context/git-workflow.md 在有项目未提交改写的情况下被升级了托管块(1.6.0→1.7.0),产生「同一文件混有炼境与项目两种来源改动」的混合文件。按「禁止字符串手术」铁律它无法拆分提交,只能交人定案,verify_onboarding 也因此持续报版本不符。管家应在制造混合文件之前提示,而不是事后由人收拾。
|
||||
- 实现(2026-10-03,分支 feat/agent-client-parity-p10):TemplateResult 新增可选 warning。apply_onboarding_pack 在模板分发与 buff 蓝图规则同步前后夹一层检测:写入前对 AGENTS.md、docs/ai-context/git-workflow.md、CLAUDE.md 跑 `git --no-optional-locks status --porcelain -uall -- <文件>`,有改动再取 `git show HEAD:./<文件>`(不在 HEAD 中按空内容算,即未跟踪/仅暂存/尚无提交),两边去掉 ONBOARDING_MANAGED 与 BLUEPRINT_MANAGED 块后比较(忽略行尾、行末空白与空行);写入后只对本次确被改写的文件给出 warning(含文件名与「块外有未提交改动,升级后将成为混合文件」),CLAUDE.md 不在模板结果里,预警时补一条结果。git 不可用、非仓库或命令失败一律静默。只提示不阻断,块外内容照旧不动。MCP 单项目输出在该文件行下加 ⚠️ 行,批量输出追加「混合文件」清单;炼境 UI 接入包结果行下显示琥珀色预警。
|
||||
- 实现(2026-10-03,分支 feat/agent-client-parity-p10):TemplateResult 新增可选 warning。apply_onboarding_pack 在模板分发与 buff 蓝图规则同步前后夹一层检测:写入前对 AGENTS.md、docs/ai-context/git-workflow.md、CLAUDE.md 跑 `git --no-optional-locks status --porcelain -uall -- <文件>`,有改动再取 `git show HEAD:./<文件>`(不在 HEAD 中按空内容算,即未跟踪/仅暂存/尚无提交),两边去掉 ONBOARDING_MANAGED 与 BLUEPRINT_MANAGED 块后比较(只忽略 CRLF/LF 与文末托管块连同其前空白,见下方审查修复);写入后只对本次确被改写的文件给出 warning(含文件名与「块外有未提交改动,升级后将成为混合文件」),CLAUDE.md 不在模板结果里,预警时补一条结果。git 不可用、非仓库或命令失败一律静默。只提示不阻断,块外内容照旧不动。MCP 单项目输出在该文件行下加 ⚠️ 行,批量输出追加「混合文件」清单;炼境 UI 接入包结果行下显示琥珀色预警。
|
||||
- verification: "2026-10-03:新增 3 个测试——outside_managed_content_strips_blocks_and_ignores_eol_and_blank_lines(纯函数:剔除两种块、忽略 CRLF 与空行、不在 HEAD 时有块外内容才算、残缺块按块外处理);mixed_file_warning_only_for_uncommitted_changes_outside_managed_blocks(临时 git 仓库,走与 apply 相同的检测夹层 + 真实 sync_blueprint_rules:git-workflow.md 块外未提交改写 → updated 且带预警、块外改写原样保留、托管块升到模板版本;未跟踪 CLAUDE.md 含项目内容 → 补 appended 结果并预警;与 HEAD 一致的 AGENTS.md 照常升级无预警;全部提交后只改托管块版本并整体转 CRLF → 升级且全部无预警、不补 CLAUDE.md 行);mixed_file_warning_is_silent_outside_git(非 git 目录升级无预警,--nocapture 确认未走跳过分支)。变异自检:把探测函数强制返回 true,后两个测试均失败,已还原。cargo test --locked:lib 270 passed / 5 ignored,0 failed;pnpm typecheck 通过;pnpm test 16 passed。"
|
||||
- known_limits: "仓库里没有 apply_onboarding_pack 的预览命令(acceptance 写的「预览」无处可挂),预警随结果返回:检测在写入前完成,但升级照常进行,调用方拿到结果时混合文件已经产生——真正「先提示再决定」要等 P8 的只报告 diff 模式复用本检测。从未提交过的炼境新建文件(块外整段是模板 TODO)在模板升版后重跑也会预警(不在 HEAD 中、无法区分块外内容来源),属安全方向的误报。buff 之外单独调用 sync_blueprint_rules(UI 同步蓝图规则、批量同步)不经此检测。仅比较 HEAD 与工作区:暂存区与工作区不同的中间状态不单独判断。"
|
||||
- review_fixes: "2026-10-03 审查修复三条:① 原比较逐行 trim_end 并丢弃全部空行,块外删空行(Markdown 并段)或加行尾空格(硬换行)会漏报——改为托管块原位留占位符,只把文末的占位符连同其前空白按 trim_end 归一(抵消炼境追加时的 trim_end + 空行),文中块两侧空行与行尾空格照常比较;② 后续步骤(buff 先同步蓝图规则、再写 DB)报错时预警随 Err 丢失——改为先标注再返回,错误信息并入混合文件名;③ 补「块外有未提交改动但托管块已是最新、本次不改写」情形,测住 annotate 的改写判断。新增测试 outside_managed_whitespace_edits_count_as_changes、mixed_file_warning_survives_failure_after_write,主场景测试补情形三。"
|
||||
- known_limits: "仓库里没有 apply_onboarding_pack 的预览命令(acceptance 写的「预览」无处可挂),预警随结果返回:检测在写入前完成,但升级照常进行,调用方拿到结果时混合文件已经产生——真正「先提示再决定」要等 P8 的只报告 diff 模式复用本检测。从未提交过的炼境新建文件(块外整段是模板 TODO)在模板升版后重跑也会预警(不在 HEAD 中、无法区分块外内容来源),属安全方向的误报。buff 之外单独调用 sync_blueprint_rules(UI 同步蓝图规则、批量同步)不经此检测。仅比较 HEAD 与工作区:暂存区与工作区不同的中间状态不单独判断。文末托管块之前、之后的纯空白增删(如块前空行数、文件末尾多出空行)为抵消炼境追加差异仍被忽略。"
|
||||
|
||||
@ -26,7 +26,7 @@ contracts:
|
||||
- ".lian-jing/context.json"
|
||||
- ".lian-jing/capability-packs.json"
|
||||
- ".claude/liangjing.json"
|
||||
doc: "`.lian-jing/context.json` 是 project_id 的工具无关单一事实来源;`.claude/liangjing.json` 仅为兼容投影;`.lian-jing/capability-packs.json` 是炼境分发基线的版本描述。lian-jing skill 以同一模板双投影:`.agents/skills/lian-jing/`(Codex、DSH 读取)与 `.claude/skills/lian-jing/`(Claude Code 不读 .agents/skills),两份内容必须一致。context.json 可选字段 mcp_scope=global 声明控制面项目(如炼境自身):Claude 不写 local 条目、DSH 不入白名单,保留全局地址与跨项目工具。迁移必须保留未知字段;验证必须返回显式状态与 upgrade_hint;升级仅覆盖炼境托管文件,绝不覆盖项目定制或手工 MCP 配置,且不得以客户端全局配置或项目 UI 身份替代项目根目录上下文。apply_onboarding_pack 的模板结果 TemplateResult 可带 warning(混合文件预警):写入前 AGENTS.md、docs/ai-context/git-workflow.md 或 CLAUDE.md 在 git 中已有托管块(ONBOARDING_MANAGED / BLUEPRINT_MANAGED)之外的未提交改动(未跟踪文件带块外内容也算;比较忽略行尾与空行),且本次确实改写了该文件时给出;只提示不阻断、块外内容不动,非 git 仓库或 git 不可用时静默。CLAUDE.md 只由蓝图规则同步改写,仅在预警时补一条结果。"
|
||||
doc: "`.lian-jing/context.json` 是 project_id 的工具无关单一事实来源;`.claude/liangjing.json` 仅为兼容投影;`.lian-jing/capability-packs.json` 是炼境分发基线的版本描述。lian-jing skill 以同一模板双投影:`.agents/skills/lian-jing/`(Codex、DSH 读取)与 `.claude/skills/lian-jing/`(Claude Code 不读 .agents/skills),两份内容必须一致。context.json 可选字段 mcp_scope=global 声明控制面项目(如炼境自身):Claude 不写 local 条目、DSH 不入白名单,保留全局地址与跨项目工具。迁移必须保留未知字段;验证必须返回显式状态与 upgrade_hint;升级仅覆盖炼境托管文件,绝不覆盖项目定制或手工 MCP 配置,且不得以客户端全局配置或项目 UI 身份替代项目根目录上下文。apply_onboarding_pack 的模板结果 TemplateResult 可带 warning(混合文件预警):写入前 AGENTS.md、docs/ai-context/git-workflow.md 或 CLAUDE.md 在 git 中已有托管块(ONBOARDING_MANAGED / BLUEPRINT_MANAGED)之外的未提交改动(未跟踪文件带块外内容也算;比较只忽略 CRLF/LF 与文末托管块连同其前空白——即炼境追加托管块带来的差异,块外空行增删与行尾空格均算改动),且本次确实改写了该文件时给出;只提示不阻断、块外内容不动,非 git 仓库或 git 不可用时静默。CLAUDE.md 只由蓝图规则同步改写,仅在预警时补一条结果。模板分发之后的步骤(lefthook 激活、buff)报错时,已改写的混合文件名并入错误信息返回。"
|
||||
|
||||
- label: Tauri command 接口(前后端契约)
|
||||
paths:
|
||||
|
||||
@ -234,23 +234,29 @@ const STRIPPED_MANAGED_BLOCKS: [(&str, &str); 2] = [
|
||||
),
|
||||
];
|
||||
|
||||
/// 去掉全部托管块(含标记)后的块外内容。忽略行尾、行末空白与空行差异——
|
||||
/// 炼境追加托管块时自己插入的空行不应被当成项目改动。
|
||||
/// 托管块剔除后在原位留下的占位符(正常文本不含 NUL)。
|
||||
const BLOCK_PLACEHOLDER: char = '\0';
|
||||
|
||||
/// 去掉全部托管块(含标记)后的块外内容。只抵消炼境自己写入带来的差异,其余空白差异
|
||||
/// (块外空行增删、行尾空格——Markdown 里分别是段落合并与硬换行)一律算项目改动:
|
||||
/// - CRLF 统一为 LF(子项目常开 core.autocrlf);
|
||||
/// - 托管块原位换成占位符:文中的块两侧空行原样参与比较(炼境只原位替换文中的块,不碰两侧);
|
||||
/// - 结尾的占位符连同其间、其前的空白整体剔除:炼境追加托管块写的是
|
||||
/// `原文.trim_end() + "\n\n" + 块 + "\n"`,追加前原文末尾的空白也被 trim_end 吃掉,
|
||||
/// 所以只有文末这段按 trim_end 归一。
|
||||
fn outside_managed_content(text: &str) -> String {
|
||||
let mut rest = text.replace("\r\n", "\n");
|
||||
let placeholder = BLOCK_PLACEHOLDER.to_string();
|
||||
for (start, end) in STRIPPED_MANAGED_BLOCKS {
|
||||
while let Some(s) = rest.find(start) {
|
||||
let Some(len) = rest[s..].find(end) else {
|
||||
break; // 残缺块(无结束标记):余下内容按块外处理
|
||||
};
|
||||
rest.replace_range(s..s + len + end.len(), "");
|
||||
rest.replace_range(s..s + len + end.len(), &placeholder);
|
||||
}
|
||||
}
|
||||
rest.lines()
|
||||
.map(str::trim_end)
|
||||
.filter(|line| !line.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
rest.trim_end_matches(|c: char| c.is_whitespace() || c == BLOCK_PLACEHOLDER)
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// HEAD 版本(None = 不在 HEAD 中:未跟踪、仅暂存或仓库尚无提交)与工作区版本的块外内容是否不同。
|
||||
@ -352,15 +358,34 @@ impl MixedFileGuard {
|
||||
|
||||
/// 模板分发与其后的写入步骤(apply 时为 lefthook 激活与 buff 蓝图规则同步)前后夹混合文件检测:
|
||||
/// 写入前快照块外未提交改动,写入后只对确被改写的文件给出预警。
|
||||
/// 后续步骤报错时文件可能已被改写(如 buff 先同步了蓝图规则、再写 DB 失败),预警是混合文件
|
||||
/// 已产生的唯一信号,不能随 Err 丢失:先标注,再把混合文件名并入错误信息。
|
||||
fn dispatch_templates_with_mixed_file_check<T>(
|
||||
root: &Path,
|
||||
then: impl FnOnce() -> Result<T, String>,
|
||||
) -> Result<(Vec<TemplateResult>, T), String> {
|
||||
let guard = MixedFileGuard::snapshot(root);
|
||||
let mut templates = dispatch_templates(&root.to_string_lossy());
|
||||
let rest = then()?;
|
||||
let rest = then();
|
||||
guard.annotate(root, &mut templates);
|
||||
Ok((templates, rest))
|
||||
match rest {
|
||||
Ok(rest) => Ok((templates, rest)),
|
||||
Err(e) => {
|
||||
let mixed: Vec<&str> = templates
|
||||
.iter()
|
||||
.filter(|t| t.warning.is_some())
|
||||
.map(|t| t.name.as_str())
|
||||
.collect();
|
||||
if mixed.is_empty() {
|
||||
Err(e)
|
||||
} else {
|
||||
Err(format!(
|
||||
"{e};注意:以下文件块外有未提交改动,本次已改写其中的炼境托管块,成为混合文件:{}",
|
||||
mixed.join("、")
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── V1:lefthook 激活(分发 ≠ 生效,文件写入后必须 lefthook install 挂上 hook)──
|
||||
@ -1292,13 +1317,18 @@ mod tests {
|
||||
// ── P10:混合文件预警 ────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn outside_managed_content_strips_blocks_and_ignores_eol_and_blank_lines() {
|
||||
fn outside_managed_content_strips_blocks_and_only_normalizes_eol_and_appended_tail() {
|
||||
let with_blocks = "# 标题\r\n\r\n项目内容\r\n\r\n<!-- ONBOARDING_MANAGED_START version=\"1.0.0\" -->\r\n炼境内容\r\n<!-- ONBOARDING_MANAGED_END -->\r\n\r\n<!-- BLUEPRINT_MANAGED_START version=\"2.0.0\" -->\n蓝图\n<!-- BLUEPRINT_MANAGED_END -->\n";
|
||||
assert_eq!(outside_managed_content(with_blocks), "# 标题\n项目内容");
|
||||
assert_eq!(outside_managed_content(with_blocks), "# 标题\n\n项目内容");
|
||||
// 之前追加的块尚未提交、只多出块前空行(原文末尾空白被追加时的 trim_end 吃掉):不算改动
|
||||
assert!(!outside_managed_differs(
|
||||
Some("# 标题\n\n项目内容\n"),
|
||||
with_blocks
|
||||
));
|
||||
assert!(!outside_managed_differs(
|
||||
Some("# 标题\n\n项目内容 \n\n\n"),
|
||||
with_blocks
|
||||
));
|
||||
assert!(outside_managed_differs(
|
||||
Some("# 标题\n项目内容"),
|
||||
&with_blocks.replace("项目内容", "改过的项目内容")
|
||||
@ -1316,6 +1346,41 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outside_managed_whitespace_edits_count_as_changes() {
|
||||
let block = |ver: &str| {
|
||||
format!(
|
||||
"<!-- ONBOARDING_MANAGED_START version=\"{ver}\" -->\n炼境内容\n<!-- ONBOARDING_MANAGED_END -->"
|
||||
)
|
||||
};
|
||||
let head = format!("# 项目文档\n\nA\n\nB\n\n{}\n", block("1.0.0"));
|
||||
// 托管块版本不同、块外一致(含 CRLF 检出):不算改动
|
||||
assert!(!outside_managed_differs(
|
||||
Some(&head),
|
||||
&format!("# 项目文档\n\nA\n\nB\n\n{}\n", block("2.0.0")).replace('\n', "\r\n")
|
||||
));
|
||||
// 删掉 A、B 间的空行(Markdown 两段并成一段):算改动
|
||||
assert!(outside_managed_differs(
|
||||
Some(&head),
|
||||
&format!("# 项目文档\n\nA\nB\n\n{}\n", block("1.0.0"))
|
||||
));
|
||||
// 给 A 加两个行尾空格(Markdown 硬换行):算改动
|
||||
assert!(outside_managed_differs(
|
||||
Some(&head),
|
||||
&format!("# 项目文档\n\nA \n\nB\n\n{}\n", block("1.0.0"))
|
||||
));
|
||||
// 文中托管块两侧的空行由项目改动:算改动(炼境只原位替换文中的块,不碰两侧)
|
||||
let middle = format!("A\n\n{}\n\nB\n", block("1.0.0"));
|
||||
assert!(!outside_managed_differs(
|
||||
Some(&middle),
|
||||
&middle.replace("1.0.0", "2.0.0")
|
||||
));
|
||||
assert!(outside_managed_differs(
|
||||
Some(&middle),
|
||||
&format!("A\n\n{}\nB\n", block("1.0.0"))
|
||||
));
|
||||
}
|
||||
|
||||
fn git_in(dir: &Path, args: &[&str]) -> bool {
|
||||
std::process::Command::new("git")
|
||||
.args([
|
||||
@ -1456,6 +1521,112 @@ mod tests {
|
||||
"无预警时不补 CLAUDE.md 结果行"
|
||||
);
|
||||
|
||||
// 情形三:块外有未提交改动、但托管块已是最新(本次不改写)→ 不预警、不补 CLAUDE.md 行
|
||||
assert!(git_in(&base, &["add", "-A"]));
|
||||
assert!(git_in(
|
||||
&base,
|
||||
&["commit", "-q", "--no-verify", "-m", "upgrade"]
|
||||
));
|
||||
let agents_now = std::fs::read_to_string(&agents_path).unwrap();
|
||||
assert!(agents_now.contains("项目说明"));
|
||||
std::fs::write(
|
||||
&agents_path,
|
||||
agents_now.replacen("项目说明", "项目说明(未提交改写)", 1),
|
||||
)
|
||||
.unwrap();
|
||||
let mut workflow_now = std::fs::read_to_string(&workflow_path).unwrap();
|
||||
workflow_now.push_str("\n块外追加的项目约定\n");
|
||||
std::fs::write(&workflow_path, workflow_now).unwrap();
|
||||
let r = apply_like(&base);
|
||||
assert_eq!(result_of(&r, "AGENTS.md").action, "skipped");
|
||||
assert_eq!(
|
||||
result_of(&r, "docs/ai-context/git-workflow.md").action,
|
||||
"skipped"
|
||||
);
|
||||
assert!(
|
||||
r.iter().all(|t| t.warning.is_none()),
|
||||
"本次没改写的文件即使块外有未提交改动也不应预警: {r:?}"
|
||||
);
|
||||
assert!(
|
||||
!r.iter().any(|t| t.name == "CLAUDE.md"),
|
||||
"CLAUDE.md 未被改写时不补结果行"
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_file_warning_survives_failure_after_write() {
|
||||
let base = std::env::temp_dir().join(format!(
|
||||
"lianjing_onb_mixed_err_test_{}",
|
||||
std::process::id()
|
||||
));
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
std::fs::create_dir_all(base.join("docs/ai-context")).unwrap();
|
||||
let workflow_path = base.join("docs/ai-context/git-workflow.md");
|
||||
let workflow = outdated_versioned(TMPL_GIT_WORKFLOW, "项目分支约定");
|
||||
std::fs::write(
|
||||
base.join("AGENTS.md"),
|
||||
outdated_versioned(TMPL_AGENTS, "项目说明"),
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(&workflow_path, &workflow).unwrap();
|
||||
assert!(git_in(&base, &["init", "-q"]));
|
||||
assert!(git_in(&base, &["add", "-A"]));
|
||||
assert!(git_in(
|
||||
&base,
|
||||
&["commit", "-q", "--no-verify", "-m", "init"]
|
||||
));
|
||||
let failing_after_write = || {
|
||||
dispatch_templates_with_mixed_file_check(&base, || {
|
||||
crate::commands::blueprint::sync_blueprint_rules(
|
||||
base.to_string_lossy().into_owned(),
|
||||
)?;
|
||||
Err::<(), String>("database is locked".into())
|
||||
})
|
||||
.map(|_| ())
|
||||
};
|
||||
|
||||
// 无块外未提交改动:错误原样返回
|
||||
assert_eq!(
|
||||
failing_after_write().unwrap_err(),
|
||||
"database is locked",
|
||||
"无混合文件时不改写错误信息"
|
||||
);
|
||||
|
||||
// 还原到旧版托管块,git-workflow.md 块外改写后再失败:错误里须带混合文件名
|
||||
assert!(git_in(
|
||||
&base,
|
||||
&[
|
||||
"checkout",
|
||||
"--",
|
||||
"AGENTS.md",
|
||||
"docs/ai-context/git-workflow.md"
|
||||
]
|
||||
));
|
||||
std::fs::write(
|
||||
&workflow_path,
|
||||
workflow.replace("项目分支约定", "项目分支约定(未提交改写)"),
|
||||
)
|
||||
.unwrap();
|
||||
let err = failing_after_write().unwrap_err();
|
||||
assert!(
|
||||
err.starts_with("database is locked")
|
||||
&& err.contains("混合文件")
|
||||
&& err.contains("docs/ai-context/git-workflow.md"),
|
||||
"后续步骤失败时预警须并入错误信息: {err}"
|
||||
);
|
||||
assert!(
|
||||
!err.contains("AGENTS.md"),
|
||||
"块外无改动的文件不应列入: {err}"
|
||||
);
|
||||
assert!(
|
||||
std::fs::read_to_string(&workflow_path)
|
||||
.unwrap()
|
||||
.contains("项目分支约定(未提交改写)"),
|
||||
"块外内容不得改动"
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user