Skip to content

fix(mv): mv fails when source has a self-reference and path shape differs from WalkBuilder output #6

@StudentWeis

Description

@StudentWeis

Summary

mdref mv <source> <dest> fails with IO error reading '<./source>': No such file or directory whenever the source file contains a Markdown link that references itself (a self-reference) and the caller passes source as a relative path that does not include a leading ./. The operation is rolled back, so no data is lost, but the move does not complete.

Steps to Reproduce

  1. From the repo root:

    cat examples/main.md | head -n 8
    # Outer main
    #
    # ![jpg test](main.jpg)
    #
    # ![jpg test](main.jpg)
    #
    # [self](main.md) [inner](inner/main.md) [sub](inner/sub/main.md)   ← self-reference on line 7
  2. Build and run mv:

    cargo build --bin mdref
    ./target/debug/mdref mv examples/main.md ./test.md
  3. Observe the error:

    Move examples/main.md -> ./test.md in .
    Error: IO error reading './examples/main.md': No such file or directory (os error 2)
    
  4. ls examples/main.md ./test.md — the source file is still there (rolled back), the destination was not created.

Calling mdref find examples/main.md works fine on the exact same path, which makes the failure surprising.

Expected Behavior

mdref mv examples/main.md ./test.md should succeed, move the file to ./test.md, rewrite the self-reference in the moved file, and update every external reference — exactly like it does when source happens to already be canonical or is passed with a ./ prefix.

Actual Behavior

The command always fails during the apply_replacements phase with the above IoRead error, regardless of whether a self-reference is actually present or not, as long as the source path as passed by the user differs textually from the path shape produced by WalkBuilder. The MoveTransaction rollback kicks in and restores the source file.

Root cause (already traced): src/core/find.rs::find_references obtains Markdown paths through src/core/util.rs::collect_markdown_files, which hands back ignore::WalkBuilder outputs verbatim. For root=".", those paths are prefixed with ./ (e.g. ./examples/main.md). src/core/mv.rs::mv_regular_file then builds replacements_by_file keyed on those ./-prefixed PathBufs, but tries to strip the self-reference entry via replacements_by_file.remove(source) using the non-prefixed source the user supplied (examples/main.md). The lookup misses, so the self-reference entry survives into the Phase-2 execute step. fs::rename moves the source away, and the subsequent apply_replacements read on the stale key fails. The same shape of mismatch exists in mv_case_only_file via move_source_replacements_to_destination.

mdref Version

0.4.4 (at commit 2ae5c5b on main, post PR #5)

Operating System

macOS

Logs / Screenshots

$ ./target/debug/mdref mv examples/main.md ./test.md
Move examples/main.md -> ./test.md in .
Error: IO error reading './examples/main.md': No such file or directory (os error 2)

$ ls examples/main.md ./test.md
ls: ./test.md: No such file or directory
examples/main.md

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions