继续收敛补丁指纹读取错误
补丁目标指纹仅将真实 NotFound 视为缺失 保留元数据、打开、正文读取和文件超限的具体错误 补充缺失、目录读取和超限回归测试
This commit is contained in:
@@ -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`,其余失败返回目标相对路径、阶段和底层正文,避免补丁结果被错误归因。
|
||||
|
||||
Reference in New Issue
Block a user