-
-
Notifications
You must be signed in to change notification settings - Fork 288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(path): improve & fix bugs in Path:rename()
#485
base: master
Are you sure you want to change the base?
Changes from all commits
7bd8300
0bee4b1
e890ac9
a5ebe96
25b0cc8
fded248
1f134d8
c0bca33
1f233a6
1e194ec
8159983
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -515,30 +515,69 @@ function Path:rmdir() | |
uv.fs_rmdir(self:absolute()) | ||
end | ||
|
||
---Rename this file or directory to the provided path (`opts.new_name`), | ||
---returning a new Path instance upon success. The rename is aborted if the | ||
---new path already exists. Relative paths are interpreted relative to the | ||
---current working directory. | ||
---@param opts { new_name: Path|string } options table containing the new name | ||
---@return Path # Path representing the new name | ||
function Path:rename(opts) | ||
-- TODO: For reference, Python's `Path.rename()` actually says/does this: | ||
-- | ||
-- > On Unix, if target exists and is a file, it will be replaced silently | ||
-- > if the user has permission. | ||
-- > | ||
-- > On Windows, if target exists, FileExistsError will be raised. target | ||
-- > can be either a string or another path object. | ||
-- | ||
-- The behavior here may differ, as an error will be thrown regardless. | ||
|
||
local self_lstat, new_lstat, status, errmsg | ||
vim.validate { opts = { opts, "t" } } | ||
vim.validate { | ||
["opts.new_name"] = { | ||
opts.new_name, | ||
function(val) | ||
return Path.is_path(val) or (type(val) == "string" and val ~= "") | ||
end, | ||
"string or Path object", | ||
}, | ||
} | ||
opts = opts or {} | ||
if not opts.new_name or opts.new_name == "" then | ||
error "Please provide the new name!" | ||
end | ||
self_lstat, errmsg = uv.fs_lstat(self.filename) | ||
|
||
-- handles `.`, `..`, `./`, and `../` | ||
if opts.new_name:match "^%.%.?/?\\?.+" then | ||
opts.new_name = { | ||
uv.fs_realpath(opts.new_name:sub(1, 3)), | ||
opts.new_name:sub(4, #opts.new_name), | ||
} | ||
end | ||
-- Cannot rename a non-existing path (lstat is needed here, `Path:exists()` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I dont understand the difference to lstat from this text. Do you mean uv.lstat? Do I need to look into Path:exit()? |
||
-- uses stat) | ||
assert(self_lstat, ("%s: %s"):format(errmsg, self.filename)) | ||
|
||
local new_path = Path:new(opts.new_name) | ||
|
||
if new_path:exists() then | ||
error "File or directory already exists!" | ||
end | ||
Comment on lines
-534
to
-536
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed this. This was a bug because For example, |
||
|
||
local status = uv.fs_rename(self:absolute(), new_path:absolute()) | ||
self.filename = new_path.filename | ||
|
||
return status | ||
new_lstat, errmsg = uv.fs_lstat(new_path.filename) | ||
local same_inode = false | ||
if new_lstat then | ||
same_inode = self_lstat.ino == new_lstat.ino and self_lstat.dev == new_lstat.dev and self_lstat.gen == new_lstat.gen | ||
end | ||
|
||
-- The following allows changing only case (e.g. fname -> Fname) on | ||
-- case-insensitive file systems, otherwise throwing if `new_name` exists as | ||
-- a different file. | ||
-- | ||
-- NOTE: To elaborate, `uv.fs_rename()` won't/shouldn't do anything if old | ||
-- and new both exist and are both hard links to the same file (inode), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing word: new both? |
||
-- however, it appears to still allow you to change the case of a filename | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/allow/allows |
||
-- on case-insensitive file systems (i.e. if `new_name` doesn't _actually_ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why the word actually? This reads like a code example would be much much simpler. |
||
-- exist as a separate file but would otherwise appear to via an lstat call; | ||
-- if it does actually exist — in which case the fs must be case-sensitive — | ||
-- idk for certain what happens b/c it needs to be tested on a case-sensitive | ||
-- fs, but it should simply result in a successful no-op according to the | ||
-- `rename(2)` docs, at least on Linux anyway). | ||
assert(not new_lstat or same_inode, "File or directory already exists!") | ||
|
||
status, errmsg = uv.fs_rename(tostring(self), tostring(new_path)) | ||
assert(status, ("%s: Rename failed!"):format(errmsg)) | ||
|
||
-- NOTE: `uv.fs_rename()` _can_ return success even if no rename actually | ||
-- occurred (see rename(2)), and this is not an error. | ||
return Path:new(new_path) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Due to the implementation of
Python's |
||
end | ||
|
||
--- Copy files or folders with defaults akin to GNU's `cp`. | ||
|
@@ -560,11 +599,14 @@ function Path:copy(opts) | |
local dest = opts.destination | ||
-- handles `.`, `..`, `./`, and `../` | ||
if not Path.is_path(dest) then | ||
if type(dest) == "string" and dest:match "^%.%.?/?\\?.+" then | ||
dest = { | ||
uv.fs_realpath(dest:sub(1, 3)), | ||
dest:sub(4, #dest), | ||
} | ||
if type(dest) == "string" then | ||
local m = dest:match "^%.%.?/?\\?.+" | ||
if m then | ||
dest = { | ||
uv.fs_realpath(dest:sub(1, #m)), | ||
dest:sub(#m + 1), | ||
} | ||
end | ||
end | ||
dest = Path:new(dest) | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should be able to CI test this.