Skip to content

map-anchors --apply can rewrite a link to the wrong heading, and crashes leaving a half-written tree #4

Description

@mairas

From the review on PR #1: #1 (comment)

map-anchors --apply rewrites translated sources in place. Two defects make that unsafe.

It can point a link at the wrong heading, non-deterministically. The loop iterates set(LINK.findall(text)) — unordered — while mutating text with str.replace, which rewrites every occurrence. If rewriting link A produces a string equal to link B, the later pass rewrites both. With English ids ['a','b'] and translated ids ['b','c'], the correct result is #a→#b, #b→#c; under PYTHONHASHSEED=3 and 5 the fixture produced [eka](#c) ja [toka](#c), under 0/1/2/4 it was correct. check-anchors passes the corrupted file because #c is a real id, so nothing downstream detects it. The precondition — a translated heading id equal to a different English heading id on the same page — is ordinary where headings like Signal K stay untranslated while neighbours move.

It crashes mid-run and leaves a half-rewritten tree. target_page calls .relative_to(Path.cwd()), which raises ValueError on any relative link resolving above the root — one .. too many. Pages processed before the bad link are already written, the summary never prints, and re-running crashes at the same place.

Fix: build the whole old→new mapping first and apply it in a single pass (one re.sub over the LINK pattern with a lookup), so no replacement can be re-read as input — sorting the set only makes the corruption deterministic. Catch the ValueError and return None, folding those links into the existing unmapped report. Buffer rewrites and write only after every page is processed.

Test: run the suite under a varied PYTHONHASHSEED; add a case where a translated id collides with a different English id on the same page; add an out-of-tree relative link; add a second --apply run asserting zero rewrites and a byte-identical file.

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