diff --git a/openhands/resolver/patching/apply.py b/openhands/resolver/patching/apply.py index 24f2266f56cf..aedc521a1cd6 100644 --- a/openhands/resolver/patching/apply.py +++ b/openhands/resolver/patching/apply.py @@ -94,12 +94,17 @@ def apply_diff(diff, text, reverse=False, use_patch=False): hunk=hunk, ) if lines[old - 1] != line: - raise HunkApplyException( - 'context line {n}, "{line}" does not match "{sl}"'.format( - n=old, line=line, sl=lines[old - 1] - ), - hunk=hunk, - ) + # Try to normalize whitespace by replacing multiple spaces with a single space + # This helps with patches that have different indentation levels + normalized_line = ' '.join(line.split()) + normalized_source = ' '.join(lines[old - 1].split()) + if normalized_line != normalized_source: + raise HunkApplyException( + 'context line {n}, "{line}" does not match "{sl}"'.format( + n=old, line=line, sl=lines[old - 1] + ), + hunk=hunk, + ) # for calculating the old line r = 0 diff --git a/tests/unit/test_patch_whitespace.py b/tests/unit/test_patch_whitespace.py new file mode 100644 index 000000000000..ef86ae41008b --- /dev/null +++ b/tests/unit/test_patch_whitespace.py @@ -0,0 +1,79 @@ +from openhands.resolver.patching.apply import apply_diff +from openhands.resolver.patching.patch import parse_patch + + +def test_patch_whitespace_mismatch(): + """Test that the patch application fails correctly when whitespace doesn't match.""" + # Original content has a line with spaces + original_content = """class Example: + def method(self): + pass + + def another(self): + pass""" + + # Patch expects an empty line (no spaces) + patch_text = """diff --git a/example.py b/example.py +index 1234567..89abcdef 100644 +--- a/example.py ++++ b/example.py +@@ -2,6 +2,10 @@ class Example: + def method(self): + pass + ++ new_field: str = "value" ++ + def another(self): + pass""" + + patch = next(parse_patch(patch_text)) + # The patch should still work because we normalize whitespace + new_content = apply_diff(patch, original_content) + assert new_content == [ + 'class Example:', + ' def method(self):', + ' pass', + ' ', + ' new_field: str = "value"', + '', + ' def another(self):', + ' pass', + ] + + +def test_patch_whitespace_match(): + """Test that the patch application succeeds when whitespace matches.""" + # Original content has an empty line (no spaces) + original_content = """class Example: + def method(self): + pass + + def another(self): + pass""" + + # Patch expects an empty line (no spaces) + patch_text = """diff --git a/example.py b/example.py +index 1234567..89abcdef 100644 +--- a/example.py ++++ b/example.py +@@ -2,6 +2,10 @@ class Example: + def method(self): + pass + ++ new_field: str = "value" ++ + def another(self): + pass""" + + patch = next(parse_patch(patch_text)) + new_content = apply_diff(patch, original_content) + assert new_content == [ + 'class Example:', + ' def method(self):', + ' pass', + '', + ' new_field: str = "value"', + '', + ' def another(self):', + ' pass', + ]