-
Notifications
You must be signed in to change notification settings - Fork 4.3k
[apply-patch] Handle multiple context lines #2596
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
original_lines.len() - 1 | ||
} else { | ||
original_lines.len() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bolinfest this more nuanced handling feels more correct to me but would love your opinion here
0aa030e
to
bd8deb1
Compare
Summary
Notable changes
Review
Suggestions
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Codex Review: Here are some suggestions.
Reply with @codex fix comments
to fix any unresolved comments.
About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you open a pull request for review, mark a draft as ready, or comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex fix this CI failure" or "@codex address that feedback".
let mut line_index: usize = 0; | ||
|
||
for chunk in chunks { | ||
// If a chunk has a `change_context`, we use seek_sequence to find it, then | ||
// adjust our `line_index` to continue from there. | ||
if let Some(ctx_line) = &chunk.change_context { | ||
if let Some(idx) = seek_sequence::seek_sequence( | ||
original_lines, | ||
std::slice::from_ref(ctx_line), | ||
line_index, | ||
false, | ||
) { | ||
line_index = idx + 1; | ||
} else { | ||
return Err(ApplyPatchError::ComputeReplacements(format!( | ||
"Failed to find context '{}' in {}", | ||
ctx_line, | ||
path.display() | ||
))); | ||
// If a chunk has context lines, we use seek_sequence to find each in order, | ||
// then adjust our `line_index` to continue from there. | ||
if !chunk.context_lines.is_empty() { | ||
let total = chunk.context_lines.len(); | ||
for (i, ctx_line) in chunk.context_lines.iter().enumerate() { | ||
if let Some(idx) = seek_sequence::seek_sequence( | ||
original_lines, | ||
std::slice::from_ref(ctx_line), | ||
line_index, | ||
false, | ||
) { | ||
line_index = idx + 1; | ||
} else { | ||
return Err(ApplyPatchError::ComputeReplacements(format!( | ||
"Failed to find context {}/{}: '{}' in {}", | ||
i + 1, | ||
total, | ||
ctx_line, | ||
path.display() | ||
))); | ||
} | ||
} | ||
} | ||
|
||
if chunk.old_lines.is_empty() { | ||
// Pure addition (no old lines). We'll add them at the end or just | ||
// before the final empty line if one exists. | ||
let insertion_idx = if original_lines.last().is_some_and(|s| s.is_empty()) { | ||
original_lines.len() - 1 | ||
// Pure addition (no old lines). | ||
// Prefer to insert at the matched context anchor if one exists and | ||
// the hunk is not explicitly marked as end-of-file. | ||
let insertion_idx = if chunk.is_end_of_file { | ||
if original_lines.last().is_some_and(|s| s.is_empty()) { | ||
original_lines.len() - 1 | ||
} else { | ||
original_lines.len() | ||
} | ||
} else if !chunk.context_lines.is_empty() { | ||
// Insert immediately after the last matched context line. | ||
line_index | ||
} else { | ||
original_lines.len() | ||
// No context provided: fall back to appending at the end (before | ||
// the trailing empty line if present). | ||
if original_lines.last().is_some_and(|s| s.is_empty()) { | ||
original_lines.len() - 1 | ||
} else { | ||
original_lines.len() | ||
} | ||
}; | ||
replacements.push((insertion_idx, 0, chunk.new_lines.clone())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1] Failing to apply multiple chunks under same outer context
compute_replacements advances line_index
past each processed chunk and then searches subsequent context lines starting from that position (lib.rs:529‑579). With multi‑level context lines, two chunks in the same class (e.g., methods A and B each prefixed with @@ class Foo
) cause the second search to begin after the class declaration so it can’t be found, aborting the patch. This prevents valid multi‑chunk patches within one scope.
Useful? React with 👍 / 👎.
bd8deb1
to
eadc5f3
Compare
eadc5f3
to
72d8bde
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM, just some small suggestions!
let insertion_idx = if chunk.is_end_of_file { | ||
if original_lines.last().is_some_and(|s| s.is_empty()) { | ||
original_lines.len() - 1 | ||
} else { | ||
original_lines.len() | ||
} | ||
} else if !chunk.context_lines.is_empty() { | ||
// Insert immediately after the last matched context line. | ||
line_index |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this correct?
let insertion_idx = if chunk.is_end_of_file { | |
if original_lines.last().is_some_and(|s| s.is_empty()) { | |
original_lines.len() - 1 | |
} else { | |
original_lines.len() | |
} | |
} else if !chunk.context_lines.is_empty() { | |
// Insert immediately after the last matched context line. | |
line_index | |
let insertion_idx = if !chunk.is_end_of_file && !chunk.context_lines.is_empty() { | |
// Insert immediately after the last matched context line. | |
line_index | |
} else if original_lines.last().is_some_and(|s| s.is_empty()) { | |
original_lines.len() - 1 | |
} else { | |
original_lines.len() | |
}; |
"output": [] | ||
} | ||
} | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
newline at eof?
@@ -106,3 +106,41 @@ async fn test_apply_patch_freeform_tool() -> anyhow::Result<()> { | |||
); | |||
Ok(()) | |||
} | |||
|
|||
#[cfg(not(target_os = "windows"))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure these don't work on Windows?
Summary
Fixes #2578 - the following example would be considered incorrect, despite it being valid according to our system instructions and our official Lark grammar.
Testing