mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Reject duplicate resolved paths in apply_patch (#37867)
## What changed - Reject patches containing multiple operations whose paths resolve to the same file, such as `duplicate.txt` and `./duplicate.txt`. - Preserve support for patches that update multiple distinct files. ## Testing - Add CLI integration coverage for distinct updates and duplicate resolved paths. GitOrigin-RevId: 0fb7f69ae31acc5c677268ffd9e38ad8d8314276
This commit is contained in:
@@ -232,6 +232,13 @@ async fn try_verify_apply_patch_args(
|
||||
let mut changes = HashMap::new();
|
||||
for hunk in hunks {
|
||||
let path = hunk.resolve_path(&effective_cwd)?;
|
||||
if changes.contains_key(&path) {
|
||||
return Err(ParseError::InvalidPatchError(format!(
|
||||
"multiple operations target {}",
|
||||
path.inferred_native_path_string()
|
||||
))
|
||||
.into());
|
||||
}
|
||||
match hunk {
|
||||
Hunk::AddFile { contents, .. } => {
|
||||
changes.insert(path, ApplyPatchFileChange::Add { content: contents });
|
||||
|
||||
@@ -437,6 +437,60 @@ D delete.txt
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn apply_patch_cli_preserves_distinct_updated_paths() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let harness = apply_patch_harness().await?;
|
||||
harness.write_file("first.txt", "first before\n").await?;
|
||||
harness.write_file("second.txt", "second before\n").await?;
|
||||
|
||||
let patch = "*** Begin Patch\n*** Update File: first.txt\n@@\n-first before\n+first after\n*** Update File: second.txt\n@@\n-second before\n+second after\n*** End Patch";
|
||||
let call_id = "apply-distinct-updates";
|
||||
mount_apply_patch(&harness, call_id, patch, "done").await;
|
||||
|
||||
harness.submit("please update both files").await?;
|
||||
|
||||
assert_regex_match(
|
||||
r"(?s)^Exit code: 0.*Success\. Updated the following files:\nM first\.txt\nM second\.txt\n?$",
|
||||
&harness.apply_patch_output(call_id).await,
|
||||
);
|
||||
assert_eq!(harness.read_file_text("first.txt").await?, "first after\n");
|
||||
assert_eq!(
|
||||
harness.read_file_text("second.txt").await?,
|
||||
"second after\n"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn apply_patch_cli_rejects_duplicate_resolved_paths() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let harness = apply_patch_harness().await?;
|
||||
harness.write_file("duplicate.txt", "before\n").await?;
|
||||
|
||||
let patch = "*** Begin Patch\n*** Update File: duplicate.txt\n@@\n-before\n+first after\n*** Update File: ./duplicate.txt\n@@\n-before\n+second after\n*** End Patch";
|
||||
let call_id = "apply-duplicate-resolved-path";
|
||||
mount_apply_patch(&harness, call_id, patch, "done").await;
|
||||
|
||||
harness.submit("please apply both updates").await?;
|
||||
|
||||
let out = harness.apply_patch_output(call_id).await;
|
||||
assert!(
|
||||
out.contains("apply_patch verification failed"),
|
||||
"expected verification failure: {out}"
|
||||
);
|
||||
assert!(
|
||||
out.contains("multiple operations target"),
|
||||
"expected duplicate-path diagnostics: {out}"
|
||||
);
|
||||
assert_eq!(harness.read_file_text("duplicate.txt").await?, "before\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn apply_patch_cli_multiple_chunks() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
Reference in New Issue
Block a user