继续收敛补丁指纹读取错误

补丁目标指纹仅将真实 NotFound 视为缺失

保留元数据、打开、正文读取和文件超限的具体错误

补充缺失、目录读取和超限回归测试
This commit is contained in:
kdletters
2026-10-06 00:45:30 +08:00
parent 4d20be776a
commit 81d06c112d
2 changed files with 79 additions and 22 deletions
@@ -191,42 +191,60 @@ fn validate_argv(executable: &Path, patch: &str) -> Result<(), String> {
Ok(())
}
fn target_fingerprints(targets: &[PatchTarget]) -> BTreeMap<String, Option<String>> {
targets
.iter()
.map(|target| {
let digest = (|| {
match std::fs::symlink_metadata(&target.absolute) {
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
return Some("missing".into())
}
Ok(_) => {}
Err(_) => return None,
}
fn target_fingerprints(
targets: &[PatchTarget],
) -> Result<BTreeMap<String, Option<String>>, String> {
let mut fingerprints = BTreeMap::new();
for target in targets {
let digest = match std::fs::symlink_metadata(&target.absolute) {
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Some("missing".into()),
Ok(_) => {
let (mut file, metadata) =
open_project_snapshot_regular_file(&target.absolute, "补丁指纹").ok()?;
open_project_snapshot_regular_file(&target.absolute, "补丁指纹").map_err(
|error| format!("补丁指纹读取失败:{}:{error}", target.relative),
)?;
if metadata.len() > TARGET_HASH_MAX_BYTES {
return None;
return Err(format!(
"补丁指纹读取失败:目标文件 {} 大小 {} bytes,超过 {} bytes 上限",
target.relative,
metadata.len(),
TARGET_HASH_MAX_BYTES
));
}
let mut digest = Sha256::new();
let mut buffer = [0u8; 16 * 1024];
let mut total = 0u64;
loop {
let count = file.read(&mut buffer).ok()?;
let count = file.read(&mut buffer).map_err(|error| {
format!(
"补丁指纹读取失败:{}:读取文件正文失败:{error}",
target.relative
)
})?;
if count == 0 {
break;
}
total += count as u64;
if total > TARGET_HASH_MAX_BYTES {
return None;
return Err(format!(
"补丁指纹读取失败:目标文件 {} 在读取期间超过 {} bytes 上限",
target.relative, TARGET_HASH_MAX_BYTES
));
}
digest.update(&buffer[..count]);
}
Some(format!("file:{:x}", digest.finalize()))
})();
(target.relative.clone(), digest)
})
.collect()
}
Err(error) => {
return Err(format!(
"补丁指纹检查失败:{}:读取文件元数据失败:{error}",
target.relative
));
}
};
fingerprints.insert(target.relative.clone(), digest);
}
Ok(fingerprints)
}
fn analytics_patch_changes(
@@ -278,7 +296,7 @@ fn run_transaction(
let targets = collect_targets(root, &parsed.hunks)?;
let executable = session.codex_executor()?;
validate_argv(&executable, &parsed.patch)?;
let before = target_fingerprints(&targets);
let before = target_fingerprints(&targets)?;
let lease = session.admit(EffectKind::Write, None)?;
let process = runtime.block_on(crate::command_exec::run_owned_codex_patch_at(
root,
@@ -317,7 +335,7 @@ fn run_transaction(
error.to_string(),
),
};
let after = target_fingerprints(&targets);
let after = target_fingerprints(&targets)?;
let changed_paths = targets
.iter()
.filter(|target| {
@@ -439,6 +457,44 @@ mod tests {
(temp, root.canonicalize().unwrap())
}
#[test]
fn target_fingerprints_keep_missing_distinct_and_report_read_failures() {
let (_temp, root) = project();
let missing = PatchTarget {
relative: "game/missing.txt".into(),
absolute: root.join("game/missing.txt"),
};
let fingerprints = target_fingerprints(std::slice::from_ref(&missing)).unwrap();
assert_eq!(
fingerprints.get("game/missing.txt"),
Some(&Some("missing".into()))
);
let directory = root.join("game/fingerprint-directory");
std::fs::create_dir_all(&directory).unwrap();
let directory_target = PatchTarget {
relative: "game/fingerprint-directory".into(),
absolute: directory,
};
let error = target_fingerprints(std::slice::from_ref(&directory_target)).unwrap_err();
assert!(error.contains("补丁指纹读取失败"), "{error}");
assert!(
error.contains("打开补丁指纹失败") || error.contains("必须是普通文件"),
"{error}"
);
let oversized = root.join("game/fingerprint-oversized.bin");
let file = std::fs::File::create(&oversized).unwrap();
file.set_len(TARGET_HASH_MAX_BYTES + 1).unwrap();
let oversized_target = PatchTarget {
relative: "game/fingerprint-oversized.bin".into(),
absolute: oversized,
};
let error = target_fingerprints(std::slice::from_ref(&oversized_target)).unwrap_err();
assert!(error.contains("超过"), "{error}");
assert!(error.contains("补丁指纹读取失败"), "{error}");
}
#[test]
fn parser_paths_include_every_move_source_and_destination_before_any_write() {
let (_temp, root) = project();
@@ -85,3 +85,4 @@ DirectProject 回合失败在确认不是客户端内部不可归类故障时,
- DirectProject 上下文的外部路径元数据读取失败原先统一成 `file-not-found`;现区分确实不存在与权限/磁盘读取错误,后者保留底层正文。
- 错误报告对话框读取最新错误事件或诊断日志失败时原先只显示“暂不可用/使用快照”;现把读取 IPC/文件正文附在状态提示中,快照回退行为保持不变。
- WorkspaceLauncher 的清单版本读取、失效事件重读和事件订阅失败原先只写日志或静默回退;现把安全正文送入启动器状态提示,仍保留已有回退刷新路径。
- Direct 补丁事务的目标指纹读取原先把元数据、打开、正文读取和文件超限都折叠为 `None`,可能误判文件缺失或继续比较;现只对确实 `NotFound` 保留 `missing`,其余失败返回目标相对路径、阶段和底层正文,避免补丁结果被错误归因。