Skip to content

feat: two-phase rename for path collision safety#78

Open
leno23 wants to merge 1 commit into
dolph:mainfrom
leno23:feat/two-phase-rename-issue-35-v2
Open

feat: two-phase rename for path collision safety#78
leno23 wants to merge 1 commit into
dolph:mainfrom
leno23:feat/two-phase-rename-issue-35-v2

Conversation

@leno23
Copy link
Copy Markdown

@leno23 leno23 commented May 30, 2026

Summary

Fixes #35. Renames are queued during the walk and applied after content rewrites finish. When source and target names overlap (chains or swaps), a two-phase quarantine rename avoids clobbering files.

Test plan

  • go test ./...
  • TestApplyRenamesSwap covers foo↔bar swap

Made with Cursor

Defer renames until after the walk completes and apply them in a
collision-safe order. Overlapping source/target names use a two-phase
quarantine via temp files; simple renames stay direct.

Fixes dolph#35
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2dedcc9a42

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rename_plan.go

for _, r := range phase2 {
log.Printf("Renaming %v to %v (phase 2)", filepath.Base(r.temp), filepath.Base(r.to))
if err := os.Rename(r.temp, r.to); err != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Refuse occupied non-source targets in two-phase renames

When any source/target chain triggers the two-phase path, phase 2 uses raw os.Rename, which overwrites an existing destination that was not one of the queued sources. For example, with files a, aa, and aaaa, running find-replace a aa queues a -> aa and aa -> aaaa; because aa is a source this takes the two-phase branch, and the phase-2 rename to aaaa silently clobbers the untouched aaaa file instead of reporting the same collision that renamePath would in the direct path.

Useful? React with 👍 / 👎.

Comment thread rename_plan.go
seq := 0
for i := range plans {
r := &plans[i]
r.temp = filepath.Join(filepath.Dir(r.from), fmt.Sprintf(".find-replace.tmp.%d.%d", os.Getpid(), seq))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep temp paths out of directories being renamed

The phase-1 temp is created inside the source's parent directory, so a descendant rename can be invalidated when an ancestor directory is also renamed later in the same two-phase batch. For example, find-replace a aa with a directory a/ containing file a and a sibling aa/ queues both a/a -> a/aa and a -> aa; phase 1 moves a/a to a/.find-replace.tmp..., then moves a itself, so phase 2 later looks for the child temp at the old a/... path and fails, leaving data stranded in the directory temp.

Useful? React with 👍 / 👎.

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.

Use two-phase rename to safely handle collisions and cycles

1 participant