Skip to content

BUG: find_diffs retains b/ prefixes after the first file in a multi-file udiff #5560

Description

@yifanxiong272

Issue

Summary

find_diffs removes standard Git a/ and b/ prefixes from the first file in a fenced unified diff, but retains the b/ prefix for the second and subsequent files.

For a two-file diff targeting file1.txt and file2.txt, the parser returns file1.txt and b/file2.txt.

Steps to reproduce

  1. Check out Aider main at commit 5dc9490bb35f9729ef2c95d00a19ccd30c26339c.

  2. Add the following test inside TestUnifiedDiffCoder in tests/basic/test_udiff.py:

def test_find_multi_diffs_with_git_prefixes(self):
    content = """```diff
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1 @@
-old one
+new one

--- a/file2.txt
+++ b/file2.txt
@@ -1 +1 @@
-old two
+new two
```
"""

    edits = find_diffs(content)

    self.assertEqual(
        [edit[0] for edit in edits],
        ["file1.txt", "file2.txt"],
    )
  1. Run:
python -m pytest tests/basic/test_udiff.py -q
  1. Observe that the newly added assertion fails while the four existing tests pass.

Expected behavior

find_diffs should normalize the standard Git prefixes for every file-header pair and return:

["file1.txt", "file2.txt"]

The parser already removes a/ and b/ from the initial header pair. A fenced multi-file diff contains the same standard header structure for each subsequent file, so those paths should be normalized consistently.

Actual behavior

find_diffs returns:

["file1.txt", "b/file2.txt"]

The focused test reports:

AssertionError: Lists differ: ['file1.txt', 'b/file2.txt'] != ['file1.txt', 'file2.txt']

First differing element 1:
'b/file2.txt'
'file2.txt'

1 failed, 4 passed

The second edit is therefore associated with b/file2.txt rather than the intended repository-relative path file2.txt.

Root cause

process_fenced_block normalizes the initial header pair:

a_fname = block[0][4:].strip()
b_fname = block[1][4:].strip()

if (a_fname.startswith("a/") or a_fname == "/dev/null") and b_fname.startswith("b/"):
    fname = b_fname[2:]

When a later header pair is encountered, however, the parser assigns the raw +++ path directly:

if line.startswith("+++ ") and hunk[-2].startswith("--- "):
    ...
    fname = line[4:].strip()

The later branch does not apply the normalization used for the initial pair.

UnifiedDiffCoder.get_edits consumes the filenames returned by find_diffs, and apply_edits subsequently resolves each filename as a repository-relative path. The retained prefix therefore propagates beyond the parser.

Possible fix direction

Apply the same paired-header normalization to every ---/+++ pair encountered by process_fenced_block, rather than only the initial pair.

Regression coverage should include a fenced multi-file diff using standard a/ and b/ prefixes and verify the normalized filename of every returned edit.

Version and model info

Aider: 0.86.3.dev, main@5dc9490bb35f9729ef2c95d00a19ccd30c26339c
Python: 3.12.13
Operating system: macOS 15.7.3
Installation: source checkout; focused pytest reproduction
Edit format: udiff
Model: not applicable; reproduced by directly invoking deterministic unified-diff parsing code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions