mirror of
https://github.com/openai/codex.git
synced 2026-10-31 19:28:49 +00:00
Add a line-ending preservation mode to apply_patch (#37757)
## Why Updating a file with `apply_patch` historically normalized its contents to LF, which can rewrite line endings outside the requested change. ## What changed - Add an opt-in `PreserveLineEndings` update mode and thread it through patch verification, application, and the standalone executable. Existing APIs keep the LF-normalizing mode by default. - Retain the original endings of untouched and context lines, including in mixed-ending files, and use the file's first line ending for inserted or replaced lines. - Track patch context separately from identical changed lines so preservation does not alter replacement ordering or matching. ## Testing Add coverage for CRLF, CR, mixed endings, trailing blank lines, repeated lines, EOF overlap rejection, and legacy LF normalization. GitOrigin-RevId: 085f0cbc57e0965aa62c6a2d0e80870424e762f5
This commit is contained in:
@@ -274,12 +274,7 @@ impl StreamingPatchParser {
|
||||
}
|
||||
|
||||
if update_line == EMPTY_CHANGE_CONTEXT_MARKER {
|
||||
chunks.push(UpdateFileChunk {
|
||||
change_context: None,
|
||||
old_lines: Vec::new(),
|
||||
new_lines: Vec::new(),
|
||||
is_end_of_file: false,
|
||||
});
|
||||
chunks.push(UpdateFileChunk::default());
|
||||
self.state.mode = StreamingParserMode::UpdateFile { hunk_line_number };
|
||||
return Ok(());
|
||||
}
|
||||
@@ -287,9 +282,7 @@ impl StreamingPatchParser {
|
||||
if let Some(change_context) = update_line.strip_prefix(CHANGE_CONTEXT_MARKER) {
|
||||
chunks.push(UpdateFileChunk {
|
||||
change_context: Some(change_context.to_string()),
|
||||
old_lines: Vec::new(),
|
||||
new_lines: Vec::new(),
|
||||
is_end_of_file: false,
|
||||
..UpdateFileChunk::default()
|
||||
});
|
||||
self.state.mode = StreamingParserMode::UpdateFile { hunk_line_number };
|
||||
return Ok(());
|
||||
@@ -313,16 +306,10 @@ impl StreamingPatchParser {
|
||||
|
||||
if line.is_empty() {
|
||||
if chunks.is_empty() {
|
||||
chunks.push(UpdateFileChunk {
|
||||
change_context: None,
|
||||
old_lines: Vec::new(),
|
||||
new_lines: Vec::new(),
|
||||
is_end_of_file: false,
|
||||
});
|
||||
chunks.push(UpdateFileChunk::default());
|
||||
}
|
||||
if let Some(chunk) = chunks.last_mut() {
|
||||
chunk.old_lines.push(String::new());
|
||||
chunk.new_lines.push(String::new());
|
||||
chunk.push_context_line(String::new());
|
||||
}
|
||||
self.state.mode = StreamingParserMode::UpdateFile { hunk_line_number };
|
||||
return Ok(());
|
||||
@@ -330,16 +317,10 @@ impl StreamingPatchParser {
|
||||
|
||||
if let Some(line_to_add) = line.strip_prefix(' ') {
|
||||
if chunks.is_empty() {
|
||||
chunks.push(UpdateFileChunk {
|
||||
change_context: None,
|
||||
old_lines: Vec::new(),
|
||||
new_lines: Vec::new(),
|
||||
is_end_of_file: false,
|
||||
});
|
||||
chunks.push(UpdateFileChunk::default());
|
||||
}
|
||||
if let Some(chunk) = chunks.last_mut() {
|
||||
chunk.old_lines.push(line_to_add.to_string());
|
||||
chunk.new_lines.push(line_to_add.to_string());
|
||||
chunk.push_context_line(line_to_add.to_string());
|
||||
}
|
||||
self.state.mode = StreamingParserMode::UpdateFile { hunk_line_number };
|
||||
return Ok(());
|
||||
@@ -347,12 +328,7 @@ impl StreamingPatchParser {
|
||||
|
||||
if let Some(line_to_add) = line.strip_prefix('+') {
|
||||
if chunks.is_empty() {
|
||||
chunks.push(UpdateFileChunk {
|
||||
change_context: None,
|
||||
old_lines: Vec::new(),
|
||||
new_lines: Vec::new(),
|
||||
is_end_of_file: false,
|
||||
});
|
||||
chunks.push(UpdateFileChunk::default());
|
||||
}
|
||||
if let Some(chunk) = chunks.last_mut() {
|
||||
chunk.new_lines.push(line_to_add.to_string());
|
||||
@@ -363,12 +339,7 @@ impl StreamingPatchParser {
|
||||
|
||||
if let Some(line_to_remove) = line.strip_prefix('-') {
|
||||
if chunks.is_empty() {
|
||||
chunks.push(UpdateFileChunk {
|
||||
change_context: None,
|
||||
old_lines: Vec::new(),
|
||||
new_lines: Vec::new(),
|
||||
is_end_of_file: false,
|
||||
});
|
||||
chunks.push(UpdateFileChunk::default());
|
||||
}
|
||||
if let Some(chunk) = chunks.last_mut() {
|
||||
chunk.old_lines.push(line_to_remove.to_string());
|
||||
@@ -445,6 +416,7 @@ mod tests {
|
||||
change_context: None,
|
||||
old_lines: vec!["old".to_string()],
|
||||
new_lines: vec!["new".to_string()],
|
||||
context_line_indices: vec![],
|
||||
is_end_of_file: false,
|
||||
}],
|
||||
}])
|
||||
@@ -635,12 +607,14 @@ mod tests {
|
||||
change_context: None,
|
||||
old_lines: vec!["old a".to_string(), "*** Update File: b.txt".to_string()],
|
||||
new_lines: vec!["new a".to_string(), "*** Update File: b.txt".to_string()],
|
||||
context_line_indices: vec![(1, 1)],
|
||||
is_end_of_file: false,
|
||||
},
|
||||
UpdateFileChunk {
|
||||
change_context: None,
|
||||
old_lines: vec!["old b".to_string()],
|
||||
new_lines: vec!["new b".to_string()],
|
||||
context_line_indices: vec![],
|
||||
is_end_of_file: false,
|
||||
},
|
||||
],
|
||||
@@ -680,6 +654,7 @@ mod tests {
|
||||
String::new(),
|
||||
"context after".to_string(),
|
||||
],
|
||||
context_line_indices: vec![(0, 0), (1, 1), (2, 2)],
|
||||
is_end_of_file: false,
|
||||
}],
|
||||
}])
|
||||
@@ -700,6 +675,7 @@ mod tests {
|
||||
change_context: None,
|
||||
old_lines: Vec::new(),
|
||||
new_lines: vec!["quux".to_string()],
|
||||
context_line_indices: vec![],
|
||||
is_end_of_file: true,
|
||||
}],
|
||||
}])
|
||||
@@ -718,6 +694,7 @@ mod tests {
|
||||
change_context: None,
|
||||
old_lines: vec!["old".to_string()],
|
||||
new_lines: vec!["new".to_string()],
|
||||
context_line_indices: vec![],
|
||||
is_end_of_file: false,
|
||||
}],
|
||||
}])
|
||||
@@ -733,6 +710,7 @@ mod tests {
|
||||
change_context: None,
|
||||
old_lines: vec!["old\r".to_string()],
|
||||
new_lines: vec!["new".to_string()],
|
||||
context_line_indices: vec![],
|
||||
is_end_of_file: false,
|
||||
}],
|
||||
}])
|
||||
@@ -769,6 +747,7 @@ mod tests {
|
||||
change_context: None,
|
||||
old_lines: vec!["old".to_string()],
|
||||
new_lines: vec!["new".to_string()],
|
||||
context_line_indices: vec![],
|
||||
is_end_of_file: false,
|
||||
}],
|
||||
}])
|
||||
@@ -782,6 +761,7 @@ mod tests {
|
||||
change_context: None,
|
||||
old_lines: vec!["old".to_string()],
|
||||
new_lines: vec!["new".to_string()],
|
||||
context_line_indices: vec![],
|
||||
is_end_of_file: false,
|
||||
}],
|
||||
}])
|
||||
|
||||
Reference in New Issue
Block a user