Skip to content

edit: review-driven fixes for --in-place, bounds checks, and silent no-ops#3786

Merged
jqnatividad merged 7 commits intomasterfrom
edit-review-fixes
Apr 29, 2026
Merged

edit: review-driven fixes for --in-place, bounds checks, and silent no-ops#3786
jqnatividad merged 7 commits intomasterfrom
edit-review-fixes

Conversation

@jqnatividad
Copy link
Copy Markdown
Collaborator

Summary

  • Fix --in-place silently dropping edits on extensionless files (vestigial extension().is_some() guard left over from the with_extensionwith_added_extension refactor).
  • Reject --in-place with stdin (-) or missing input rather than calling std::fs::rename("-", "-.bak").
  • Replace headers.len() - 1 with checked_sub(1) so column == "_" on empty headers returns "Invalid column selected." instead of panicking.
  • Numeric column index out of range and row index out of range now return clear errors instead of silently rewriting the file unchanged.
  • Only allocate NamedTempFile when --in-place is actually set; build the target row via a single ByteRecord instead of a per-field write loop.

Test plan

  • cargo test test_edit -F all_features (10/10 pass, including 4 new tests for extensionless in-place, stdin rejection, out-of-range row, out-of-range column index)
  • cargo build --locked --bin qsv -F all_features clean
  • cargo clippy --bin qsv -F all_features clean

🤖 Generated with Claude Code

…o-ops

- Remove vestigial extension() guard so --in-place works on extensionless files
- Reject --in-place with stdin or missing input instead of mis-renaming "-"
- checked_sub on headers.len() avoids underflow when column == "_" on empty headers
- Numeric column index out of range now errors instead of silently no-op'ing
- Track row match; emit "Row N not found." instead of silently rewriting unchanged
- Only allocate NamedTempFile when --in-place is set
- Build target row via single ByteRecord instead of per-field write loop
- Add tests for extensionless in-place, stdin rejection, and out-of-range row/column

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented Apr 29, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 16 complexity

Metric Results
Complexity 16

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

jqnatividad and others added 5 commits April 29, 2026 10:04
- stdout/--output mode now warns and passes input through unchanged when
  the row is out of range (exit 0); --in-place still errors so the .bak
  rename is skipped
- Refuse to overwrite a pre-existing .bak file in --in-place mode
- Use checked_add on row+1 to avoid usize::MAX overflow
- Document new --in-place .bak collision behavior and stdout warn-vs-error
  semantics in USAGE
- Add tests for unknown column name, --in-place out-of-range row, stdout
  out-of-range row pass-through, and pre-existing .bak collision

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace TOCTOU exists-check with hard_link reservation so a concurrent
  process can't clobber an existing .bak between the check and the rename
- Reword stdout warning to "input passed through unchanged" since --output
  does write a file (just unmodified relative to input)
- Test stdout pass-through now also asserts the stderr warning is emitted

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace hard_link reservation with create_new on the .bak path: works
  on FAT32/exFAT/SMB filesystems where hard_link is unsupported, while
  still closing the TOCTOU window
- Reject symlinks for --in-place up front rather than silently resolving
  through them and leaving a hard-linked backup pointing at the target
- Place the tempfile in the input's parent directory and persist it via
  rename so input_path is never missing during the swap (no gap window)
- Add unix-only test that --in-place rejects symlinks and leaves both
  the symlink target and any .bak paths untouched
- Document symlink rejection in USAGE

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Roll back the .bak rename if tempfile.persist fails so the user isn't
  left with a missing input_path; surface backup location in the error
- Correct the comment to acknowledge a small window (two renames) rather
  than claim "no gap"
- Narrow USAGE wording from "Symlinks" to "Symbolic links" and note that
  other Windows reparse points (junction points) are not detected

Coverage for the new create_new path is already exercised end-to-end by
edit_in_place + edit_in_place_existing_bak_errors (both AlreadyExists
and success branches).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Branch the persist-failure error message on the actual rollback result
  ("original restored from <bak>" vs "rollback also failed ...; original
  remains at <bak>") so the user gets a deterministic statement about
  where their data is, instead of an ambiguous parenthetical
- Add an inline rationale for accepting the small two-rename window
  (copy-then-persist would double disk I/O on every successful edit) so
  future readers don't try to "fix" it again
- Note inline that the persist-failure rollback branch is not directly
  covered by automated tests because reliably forcing the failure
  requires platform-specific filesystem manipulation

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Inline citation of std::fs::rename's documented cross-platform replace
behavior so future reviewers don't flag the create_new placeholder +
rename pattern as broken on Windows. The only refused case is
directory-on-directory, which doesn't apply to our regular-file
placeholder.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jqnatividad jqnatividad merged commit f8fa6e0 into master Apr 29, 2026
11 of 15 checks passed
@jqnatividad jqnatividad deleted the edit-review-fixes branch April 29, 2026 15:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants