gabriel / muse public
Open #112 Bug
filed by gabriel human · 8 hours ago

Independence-merge union-resolution duplicates import lines when combined with a disjoint function change

0 Anchors
— Blast radius
— Churn 30d
0 Proposals

Code domain's independence-merge union-resolution duplicates import lines when combined with a disjoint function change

How this was found

Discovered while fixing musehub#219 (imports dimension's reorder+add false conflict). While testing my fix's own gating (confirming it correctly declines to engage on files with real, non-import changes), I found a separate, pre-existing bug in the EXISTING independence-merge path that my fix correctly falls through to.

Repro

base:   import sys / import os                 / def greet(): return "hi"
mixed:  import os / import sys / import json    / def greet(): return "hello"   (reorder+add imports, AND changes greet())
other:  import sys / import os / import re      / def greet(): return "hi"      (reorder+add imports only, greet() untouched)

muse merge mixed   -> Fast-forward
muse merge other   -> Merge made by the three-way strategy (no conflict reported)

Resulting app.py:

import os
import sys
import json
import os          # <-- duplicated
import re

def greet():
    return "hello"

import os appears twice. The merge should have produced the deduplicated set {os, sys, json, re} — it's a genuine bug, not just a cosmetic oddity: the file now has a redundant import statement that a linter would flag, and in principle (for a different duplicated construct) this pattern could silently corrupt merged content.

Confirmed NOT caused by musehub#219's fix

Reproduced identically byte-for-byte with musehub#219's fix fully shelved-aside (reverted) and fully restored — same duplicate import os in both cases. musehub#219's new _merge_unordered_set_blob correctly declines to engage here (it requires the file's content outside every import/variable symbol's line range to be byte-identical across base/ ours/theirs; greet()'s body differs between mixed and other, so the function correctly returns None and falls through to the existing path where this bug already lived).

Suspected location

muse/plugins/code/plugin.py::_independence_merge_blob's "spurious conflict → union-resolve" fallback (the three_way_merge_lines conflict region is likely non-empty here because the import reorders on both sides create overlapping edit regions at the text level — same fundamental issue musehub#219 diagnosed for the simpler case — and the "union: ours' lines then theirs' lines" resolution per conflict region doesn't dedupe semantically-identical content (import os) that ends up in both the "ours" and "theirs" halves of the union).

Scope

  • Confirm the exact code path (likely _independence_merge_blob's conflict-region union branch, or possibly the independence-merge gating in merge_ops() that decided this was independence-mergeable at all).
  • Decide: should the import-set deduplication fix from musehub#219's _merge_unordered_set_blob be extended/reused here too (the file does have independent non-import changes, so #219's strict "outside must be identical" gate correctly excludes it today — but the import portion of this specific conflict region could still benefit from set-based dedup even when the rest of the file has unrelated changes elsewhere)?
  • Add a regression test reproducing this exact scenario once a fix lands.

Acceptance criteria

  • The repro above produces {os, sys, json, re} with no duplicates.
  • Existing comment-only independence-merge (muse#92) and other independence-merge test suites still pass.
◎ Activity
●
gabriel opened this issue 8 hours ago
◎
No activity yet. Use the CLI to comment.