Skip to content

Commit b9bba09

Browse files
authored
fix: eliminate runtime dependency on patch(1) for apply_patch (openai#718)
When processing an `apply_patch` tool call, we were already computing the new file content in order to compute the unified diff. Before this PR, we were shelling out to `patch(1)` to apply the unified diff once the user accepted the change, but this updates the code to just retain the new file content and use it to write the file when the user accepts. This simplifies deployment because it no longer assumes `patch(1)` is on the host. Note this change is internal to the Codex agent and does not affect `protocol.rs`.
1 parent d09dbba commit b9bba09

File tree

2 files changed

+53
-31
lines changed

2 files changed

+53
-31
lines changed

codex-rs/apply-patch/src/lib.rs

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ pub enum ApplyPatchFileChange {
8686
Update {
8787
unified_diff: String,
8888
move_path: Option<PathBuf>,
89+
/// new_content that will result after the unified_diff is applied.
90+
new_content: String,
8991
},
9092
}
9193

@@ -126,7 +128,10 @@ pub fn maybe_parse_apply_patch_verified(argv: &[String]) -> MaybeApplyPatchVerif
126128
move_path,
127129
chunks,
128130
} => {
129-
let unified_diff = match unified_diff_from_chunks(&path, &chunks) {
131+
let ApplyPatchFileUpdate {
132+
unified_diff,
133+
content: contents,
134+
} = match unified_diff_from_chunks(&path, &chunks) {
130135
Ok(diff) => diff,
131136
Err(e) => {
132137
return MaybeApplyPatchVerified::CorrectnessError(e);
@@ -137,6 +142,7 @@ pub fn maybe_parse_apply_patch_verified(argv: &[String]) -> MaybeApplyPatchVerif
137142
ApplyPatchFileChange::Update {
138143
unified_diff,
139144
move_path,
145+
new_content: contents,
140146
},
141147
);
142148
}
@@ -516,24 +522,35 @@ fn apply_replacements(
516522
lines
517523
}
518524

525+
/// Intended result of a file update for apply_patch.
526+
#[derive(Debug, Eq, PartialEq)]
527+
pub struct ApplyPatchFileUpdate {
528+
unified_diff: String,
529+
content: String,
530+
}
531+
519532
pub fn unified_diff_from_chunks(
520533
path: &Path,
521534
chunks: &[UpdateFileChunk],
522-
) -> std::result::Result<String, ApplyPatchError> {
535+
) -> std::result::Result<ApplyPatchFileUpdate, ApplyPatchError> {
523536
unified_diff_from_chunks_with_context(path, chunks, 1)
524537
}
525538

526539
pub fn unified_diff_from_chunks_with_context(
527540
path: &Path,
528541
chunks: &[UpdateFileChunk],
529542
context: usize,
530-
) -> std::result::Result<String, ApplyPatchError> {
543+
) -> std::result::Result<ApplyPatchFileUpdate, ApplyPatchError> {
531544
let AppliedPatch {
532545
original_contents,
533546
new_contents,
534547
} = derive_new_contents_from_chunks(path, chunks)?;
535548
let text_diff = TextDiff::from_lines(&original_contents, &new_contents);
536-
Ok(text_diff.unified_diff().context_radius(context).to_string())
549+
let unified_diff = text_diff.unified_diff().context_radius(context).to_string();
550+
Ok(ApplyPatchFileUpdate {
551+
unified_diff,
552+
content: new_contents,
553+
})
537554
}
538555

539556
/// Print the summary of changes in git-style format.
@@ -898,7 +915,11 @@ PATCH"#,
898915
-qux
899916
+QUX
900917
"#;
901-
assert_eq!(expected_diff, diff);
918+
let expected = ApplyPatchFileUpdate {
919+
unified_diff: expected_diff.to_string(),
920+
content: "foo\nBAR\nbaz\nQUX\n".to_string(),
921+
};
922+
assert_eq!(expected, diff);
902923
}
903924

904925
#[test]
@@ -930,7 +951,11 @@ PATCH"#,
930951
+FOO
931952
bar
932953
"#;
933-
assert_eq!(expected_diff, diff);
954+
let expected = ApplyPatchFileUpdate {
955+
unified_diff: expected_diff.to_string(),
956+
content: "FOO\nbar\nbaz\n".to_string(),
957+
};
958+
assert_eq!(expected, diff);
934959
}
935960

936961
#[test]
@@ -963,7 +988,11 @@ PATCH"#,
963988
-baz
964989
+BAZ
965990
"#;
966-
assert_eq!(expected_diff, diff);
991+
let expected = ApplyPatchFileUpdate {
992+
unified_diff: expected_diff.to_string(),
993+
content: "foo\nbar\nBAZ\n".to_string(),
994+
};
995+
assert_eq!(expected, diff);
967996
}
968997

969998
#[test]
@@ -993,7 +1022,11 @@ PATCH"#,
9931022
baz
9941023
+quux
9951024
"#;
996-
assert_eq!(expected_diff, diff);
1025+
let expected = ApplyPatchFileUpdate {
1026+
unified_diff: expected_diff.to_string(),
1027+
content: "foo\nbar\nbaz\nquux\n".to_string(),
1028+
};
1029+
assert_eq!(expected, diff);
9971030
}
9981031

9991032
#[test]
@@ -1032,7 +1065,7 @@ PATCH"#,
10321065

10331066
let diff = unified_diff_from_chunks(&path, chunks).unwrap();
10341067

1035-
let expected = r#"@@ -1,6 +1,7 @@
1068+
let expected_diff = r#"@@ -1,6 +1,7 @@
10361069
a
10371070
-b
10381071
+B
@@ -1044,6 +1077,11 @@ PATCH"#,
10441077
+g
10451078
"#;
10461079

1080+
let expected = ApplyPatchFileUpdate {
1081+
unified_diff: expected_diff.to_string(),
1082+
content: "a\nB\nc\nd\nE\nf\ng\n".to_string(),
1083+
};
1084+
10471085
assert_eq!(expected, diff);
10481086

10491087
let mut stdout = Vec::new();

codex-rs/core/src/codex.rs

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ use std::collections::HashSet;
33
use std::io::Write;
44
use std::path::Path;
55
use std::path::PathBuf;
6-
use std::process::Command;
7-
use std::process::Stdio;
86
use std::sync::Arc;
97
use std::sync::Mutex;
108

@@ -1346,6 +1344,7 @@ fn convert_apply_patch_to_protocol(
13461344
ApplyPatchFileChange::Update {
13471345
unified_diff,
13481346
move_path,
1347+
new_content: _new_content,
13491348
} => FileChange::Update {
13501349
unified_diff: unified_diff.clone(),
13511350
move_path: move_path.clone(),
@@ -1400,28 +1399,10 @@ fn apply_changes_from_apply_patch(
14001399
deleted.push(path.clone());
14011400
}
14021401
ApplyPatchFileChange::Update {
1403-
unified_diff,
1402+
unified_diff: _unified_diff,
14041403
move_path,
1404+
new_content,
14051405
} => {
1406-
// TODO(mbolin): `patch` is not guaranteed to be available.
1407-
// Allegedly macOS provides it, but minimal Linux installs
1408-
// might omit it.
1409-
Command::new("patch")
1410-
.arg(path)
1411-
.arg("-p0")
1412-
.stdout(Stdio::null())
1413-
.stderr(Stdio::null())
1414-
.stdin(Stdio::piped())
1415-
.spawn()
1416-
.and_then(|mut child| {
1417-
let mut stdin = child.stdin.take().unwrap();
1418-
stdin.write_all(unified_diff.as_bytes())?;
1419-
stdin.flush()?;
1420-
// Drop stdin to send EOF.
1421-
drop(stdin);
1422-
child.wait()
1423-
})
1424-
.with_context(|| format!("Failed to apply patch to {}", path.display()))?;
14251406
if let Some(move_path) = move_path {
14261407
if let Some(parent) = move_path.parent() {
14271408
if !parent.as_os_str().is_empty() {
@@ -1433,11 +1414,14 @@ fn apply_changes_from_apply_patch(
14331414
})?;
14341415
}
14351416
}
1417+
14361418
std::fs::rename(path, move_path)
14371419
.with_context(|| format!("Failed to rename file {}", path.display()))?;
1420+
std::fs::write(move_path, new_content)?;
14381421
modified.push(move_path.clone());
14391422
deleted.push(path.clone());
14401423
} else {
1424+
std::fs::write(path, new_content)?;
14411425
modified.push(path.clone());
14421426
}
14431427
}

0 commit comments

Comments
 (0)