aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2024-08-06 20:49:59 +0800
committerGitHub <noreply@github.com>2024-08-06 12:49:59 +0000
commit28fbba2092adb9659253434605cb94252241f5e0 (patch)
treee7665e98c3c87f3b57ab348d143da98196efdfcc
parentb5f92c4e5c7428fe1c1f91620f4b545b30b49855 (diff)
downloadrneovim-28fbba2092adb9659253434605cb94252241f5e0.tar.gz
rneovim-28fbba2092adb9659253434605cb94252241f5e0.tar.bz2
rneovim-28fbba2092adb9659253434605cb94252241f5e0.zip
vim-patch:9.1.0465: missing filecopy() function (#29989)
Problem: missing filecopy() function Solution: implement filecopy() Vim script function (Shougo Matsushita) closes: vim/vim#12346 https://github.com/vim/vim/commit/60c8743ab6c90e402e6ed49d27455ef7e5698abe Co-authored-by: Shougo Matsushita <Shougo.Matsu@gmail.com>
-rw-r--r--runtime/doc/builtin.txt9
-rw-r--r--runtime/doc/usr_41.txt1
-rw-r--r--runtime/lua/vim/_meta/vimfn.lua13
-rw-r--r--src/nvim/eval.lua17
-rw-r--r--src/nvim/eval/fs.c21
-rw-r--r--src/nvim/fileio.c48
-rw-r--r--test/old/testdir/test_filecopy.vim72
7 files changed, 174 insertions, 7 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index ad50d271b6..97baed6cc9 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1610,6 +1610,15 @@ feedkeys({string} [, {mode}]) *feedkeys()*
Return value is always 0.
+filecopy({from}, {to}) *filecopy()*
+ Copy the file pointed to by the name {from} to {to}. The
+ result is a Number, which is |TRUE| if the file was copied
+ successfully, and |FALSE| when it failed.
+ If a file with name {to} already exists, it will fail.
+ Note that it does not handle directories (yet).
+
+ This function is not available in the |sandbox|.
+
filereadable({file}) *filereadable()*
The result is a Number, which is |TRUE| when a file with the
name {file} exists, and can be read. If {file} doesn't exist,
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index ab2eecdfaf..b3911dfd09 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -855,6 +855,7 @@ System functions and manipulation of files:
readblob() read a file into a Blob
readdir() get a List of file names in a directory
writefile() write a List of lines or Blob into a file
+ filecopy() copy a file {from} to {to}
Date and Time: *date-functions* *time-functions*
getftime() get last modification time of a file
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index 4ec6925134..4f9cc881c1 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -1993,6 +1993,19 @@ function vim.fn.feedkeys(string, mode) end
--- @return any
function vim.fn.file_readable(file) end
+--- Copy the file pointed to by the name {from} to {to}. The
+--- result is a Number, which is |TRUE| if the file was copied
+--- successfully, and |FALSE| when it failed.
+--- If a file with name {to} already exists, it will fail.
+--- Note that it does not handle directories (yet).
+---
+--- This function is not available in the |sandbox|.
+---
+--- @param from string
+--- @param to string
+--- @return 0|1
+function vim.fn.filecopy(from, to) end
+
--- The result is a Number, which is |TRUE| when a file with the
--- name {file} exists, and can be read. If {file} doesn't exist,
--- or is a directory, the result is |FALSE|. {file} is any
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 4bb82e5b3e..430ee20081 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -2521,6 +2521,23 @@ M.funcs = {
params = { { 'file', 'string' } },
signature = 'file_readable({file})',
},
+ filecopy = {
+ args = 2,
+ base = 1,
+ desc = [[
+ Copy the file pointed to by the name {from} to {to}. The
+ result is a Number, which is |TRUE| if the file was copied
+ successfully, and |FALSE| when it failed.
+ If a file with name {to} already exists, it will fail.
+ Note that it does not handle directories (yet).
+
+ This function is not available in the |sandbox|.
+ ]],
+ name = 'filecopy',
+ params = { { 'from', 'string' }, { 'to', 'string' } },
+ returns = '0|1',
+ signature = 'filecopy({from}, {to})',
+ },
filereadable = {
args = 1,
base = 1,
diff --git a/src/nvim/eval/fs.c b/src/nvim/eval/fs.c
index 9719caa52e..381fee1a3f 100644
--- a/src/nvim/eval/fs.c
+++ b/src/nvim/eval/fs.c
@@ -154,6 +154,27 @@ void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_string = path;
}
+/// "filecopy()" function
+void f_filecopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = false;
+
+ if (check_secure()
+ || tv_check_for_string_arg(argvars, 0) == FAIL
+ || tv_check_for_string_arg(argvars, 1) == FAIL) {
+ return;
+ }
+
+ const char *from = tv_get_string(&argvars[0]);
+
+ FileInfo from_info;
+ if (os_fileinfo_link(from, &from_info)
+ && (S_ISREG(from_info.stat.st_mode) || S_ISLNK(from_info.stat.st_mode))) {
+ rettv->vval.v_number
+ = vim_copyfile(tv_get_string(&argvars[0]), tv_get_string(&argvars[1])) == OK;
+ }
+}
+
/// "filereadable()" function
void f_filereadable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 6fc1d0717a..cfce9c2954 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -2654,7 +2654,6 @@ static int rename_with_tmp(const char *const from, const char *const to)
int vim_rename(const char *from, const char *to)
FUNC_ATTR_NONNULL_ALL
{
- char *errmsg = NULL;
bool use_tmp_file = false;
// When the names are identical, there is nothing to do. When they refer
@@ -2698,13 +2697,49 @@ int vim_rename(const char *from, const char *to)
}
// Rename() failed, try copying the file.
+ int ret = vim_copyfile(from, to);
+ if (ret != OK) {
+ return -1;
+ }
+
+ if (os_fileinfo(from, &from_info)) {
+ os_remove(from);
+ }
+
+ return 0;
+}
+
+/// Create the new file with same permissions as the original.
+/// Return -1 for failure, 0 for success.
+int vim_copyfile(const char *from, const char *to)
+{
+ char *errmsg = NULL;
+
+#ifdef HAVE_READLINK
+ FileInfo from_info;
+ if (os_fileinfo_link(from, &from_info) && S_ISLNK(from_info.stat.st_mode)) {
+ int ret = FAIL;
+
+ char linkbuf[MAXPATHL + 1];
+ ssize_t len = readlink(from, linkbuf, MAXPATHL);
+ if (len > 0) {
+ linkbuf[len] = NUL;
+
+ // Create link
+ ret = symlink(linkbuf, to);
+ }
+
+ return ret == 0 ? OK : FAIL;
+ }
+#endif
+
int perm = os_getperm(from);
// For systems that support ACL: get the ACL from the original file.
vim_acl_T acl = os_get_acl(from);
int fd_in = os_open(from, O_RDONLY, 0);
if (fd_in < 0) {
os_free_acl(acl);
- return -1;
+ return FAIL;
}
// Create the new file with same permissions as the original.
@@ -2712,7 +2747,7 @@ int vim_rename(const char *from, const char *to)
if (fd_out < 0) {
close(fd_in);
os_free_acl(acl);
- return -1;
+ return FAIL;
}
// Avoid xmalloc() here as vim_rename() is called by buf_write() when nvim
@@ -2722,7 +2757,7 @@ int vim_rename(const char *from, const char *to)
close(fd_out);
close(fd_in);
os_free_acl(acl);
- return -1;
+ return FAIL;
}
int n;
@@ -2749,10 +2784,9 @@ int vim_rename(const char *from, const char *to)
os_free_acl(acl);
if (errmsg != NULL) {
semsg(errmsg, to);
- return -1;
+ return FAIL;
}
- os_remove(from);
- return 0;
+ return OK;
}
static bool already_warned = false;
diff --git a/test/old/testdir/test_filecopy.vim b/test/old/testdir/test_filecopy.vim
new file mode 100644
index 0000000000..b526dce7b8
--- /dev/null
+++ b/test/old/testdir/test_filecopy.vim
@@ -0,0 +1,72 @@
+" Test filecopy()
+
+source check.vim
+source shared.vim
+
+func Test_copy_file_to_file()
+ call writefile(['foo'], 'Xcopy1')
+
+ call assert_true(filecopy('Xcopy1', 'Xcopy2'))
+
+ call assert_equal(['foo'], readfile('Xcopy2'))
+
+ " When the destination file already exists, it should not be overwritten.
+ call writefile(['foo'], 'Xcopy1')
+ call writefile(['bar'], 'Xcopy2', 'D')
+ call assert_false(filecopy('Xcopy1', 'Xcopy2'))
+ call assert_equal(['bar'], readfile('Xcopy2'))
+
+ call delete('Xcopy2')
+ call delete('Xcopy1')
+endfunc
+
+func Test_copy_symbolic_link()
+ CheckUnix
+
+ call writefile(['text'], 'Xtestfile', 'D')
+ silent !ln -s -f Xtestfile Xtestlink
+
+ call assert_true(filecopy('Xtestlink', 'Xtestlink2'))
+ call assert_equal('link', getftype('Xtestlink2'))
+ call assert_equal(['text'], readfile('Xtestlink2'))
+
+ " When the destination file already exists, it should not be overwritten.
+ call assert_false(filecopy('Xtestlink', 'Xtestlink2'))
+
+ call delete('Xtestlink2')
+ call delete('Xtestlink')
+ call delete('Xtestfile')
+endfunc
+
+func Test_copy_dir_to_dir()
+ call mkdir('Xcopydir1')
+ call writefile(['foo'], 'Xcopydir1/Xfilecopy')
+ call mkdir('Xcopydir2')
+
+ " Directory copy is not supported
+ call assert_false(filecopy('Xcopydir1', 'Xcopydir2'))
+
+ call delete('Xcopydir2', 'rf')
+ call delete('Xcopydir1', 'rf')
+endfunc
+
+func Test_copy_fails()
+ CheckUnix
+
+ call writefile(['foo'], 'Xfilecopy', 'D')
+
+ " Can't copy into a non-existing directory.
+ call assert_false(filecopy('Xfilecopy', 'Xdoesnotexist/Xfilecopy'))
+
+ " Can't copy a non-existing file.
+ call assert_false(filecopy('Xdoesnotexist', 'Xfilecopy2'))
+ call assert_equal('', glob('Xfilecopy2'))
+
+ " Can't copy to en empty file name.
+ call assert_false(filecopy('Xfilecopy', ''))
+
+ call assert_fails('call filecopy("Xfilecopy", [])', 'E1174:')
+ call assert_fails('call filecopy(0z, "Xfilecopy")', 'E1174:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab