vim-patch:9.0.0411: only created files can be cleaned up with one call

Problem:    Only created files can be cleaned up with one call.
Solution:   Add flags to mkdir() to delete with a deferred function.
            Expand the writefile() name to a full path to handle changing
            directory.

6f14da15ac

vim-patch:8.2.3742: dec mouse test fails without gnome terminfo entry

Problem:    Dec mouse test fails without gnome terminfo entry.
Solution:   Check if there is a gnome entry. Also fix 'acd' test on
            MS-Windows. (Dominique Pellé, closes vim/vim#9282)

f589fd3e10

Cherry-pick test_autochdir.vim changes from patch 9.0.0313.
Cherry-pick test_autocmd.vim changes from patch 9.0.0323.

Co-authored-by: Bram Moolenaar <Bram@vim.org>
This commit is contained in:
zeertzjq
2023-04-16 11:46:17 +08:00
parent 7b05ddbb72
commit f39b33ee49
14 changed files with 166 additions and 44 deletions

View File

@ -5572,8 +5572,24 @@ mkdir({name} [, {flags} [, {prot}]])
When {flags} is present it must be a string. An empty string
has no effect.
If {flags} is "p" then intermediate directories are created as
necessary.
If {flags} contains "p" then intermediate directories are
created as necessary.
If {flags} contains "D" then {name} is deleted at the end of
the current function, as with: >
defer delete({name}, 'd')
<
If {flags} contains "R" then {name} is deleted recursively at
the end of the current function, as with: >
defer delete({name}, 'rf')
< Note that when {name} has more than one part and "p" is used
some directories may already exist. Only the first one that
is created and what it contains is scheduled to be deleted.
E.g. when using: >
call mkdir('subdir/tmp/autoload', 'pR')
< and "subdir" already exists then "subdir/tmp" will be
scheduled for deletion, like with: >
defer delete('subdir/tmp', 'rf')
If {prot} is given it is used to set the protection bits of
the new directory. The default is 0o755 (rwxr-xr-x: r/w for

View File

@ -4892,6 +4892,9 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
*path_tail_with_sep((char *)dir) = NUL;
}
bool defer = false;
bool defer_recurse = false;
char *created = NULL;
if (argvars[1].v_type != VAR_UNKNOWN) {
if (argvars[2].v_type != VAR_UNKNOWN) {
prot = (int)tv_get_number_chk(&argvars[2], NULL);
@ -4899,9 +4902,17 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
}
if (strcmp(tv_get_string(&argvars[1]), "p") == 0) {
const char *arg2 = tv_get_string(&argvars[1]);
defer = vim_strchr(arg2, 'D') != NULL;
defer_recurse = vim_strchr(arg2, 'R') != NULL;
if ((defer || defer_recurse) && !can_add_defer()) {
return;
}
if (vim_strchr(arg2, 'p') != NULL) {
char *failed_dir;
int ret = os_mkdir_recurse(dir, prot, &failed_dir);
int ret = os_mkdir_recurse(dir, prot, &failed_dir,
defer || defer_recurse ? &created : NULL);
if (ret != 0) {
semsg(_(e_mkdir), failed_dir, os_strerror(ret));
xfree(failed_dir);
@ -4909,10 +4920,27 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
rettv->vval.v_number = OK;
return;
}
}
rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
if (rettv->vval.v_number == FAIL) {
rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
}
// Handle "D" and "R": deferred deletion of the created directory.
if (rettv->vval.v_number == OK
&& created == NULL && (defer || defer_recurse)) {
created = FullName_save(dir, false);
}
if (created != NULL) {
typval_T tv[2];
tv[0].v_type = VAR_STRING;
tv[0].v_lock = VAR_UNLOCKED;
tv[0].vval.v_string = created;
tv[1].v_type = VAR_STRING;
tv[1].v_lock = VAR_UNLOCKED;
tv[1].vval.v_string = xstrdup(defer_recurse ? "rf" : "d");
add_defer("delete", 2, tv);
}
}
/// "mode()" function
@ -9332,8 +9360,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
if (defer && get_current_funccal() == NULL) {
semsg(_(e_str_not_inside_function), "defer");
if (defer && !can_add_defer()) {
return;
}
@ -9351,7 +9378,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
typval_T tv = {
.v_type = VAR_STRING,
.v_lock = VAR_UNLOCKED,
.vval.v_string = xstrdup(fname),
.vval.v_string = FullName_save(fname, false),
};
add_defer("delete", 1, &tv);
}

View File

@ -3153,6 +3153,17 @@ static int ex_defer_inner(char *name, char **arg, const partial_T *const partial
return OK;
}
/// Return true if currently inside a function call.
/// Give an error message and return FALSE when not.
bool can_add_defer(void)
{
if (get_current_funccal() == NULL) {
semsg(_(e_str_not_inside_function), "defer");
return false;
}
return true;
}
/// Add a deferred call for "name" with arguments "argvars[argcount]".
/// Consumes "argvars[]".
/// Caller must check that current_funccal is not NULL.

View File

@ -2536,7 +2536,7 @@ static int buf_write_make_backup(char *fname, bool append, FileInfo *file_info_o
if (*dirp == NUL && !os_isdir(IObuff)) {
int ret;
char *failed_dir;
if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir)) != 0) {
if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir, NULL)) != 0) {
semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"),
failed_dir, os_strerror(ret));
xfree(failed_dir);
@ -2679,7 +2679,7 @@ nobackup:
if (*dirp == NUL && !os_isdir(IObuff)) {
int ret;
char *failed_dir;
if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir)) != 0) {
if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir, NULL)) != 0) {
semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"),
failed_dir, os_strerror(ret));
xfree(failed_dir);

View File

@ -76,7 +76,7 @@ static void log_path_init(void)
char *failed_dir = NULL;
bool log_dir_failure = false;
if (!os_isdir(loghome)) {
log_dir_failure = (os_mkdir_recurse(loghome, 0700, &failed_dir) != 0);
log_dir_failure = (os_mkdir_recurse(loghome, 0700, &failed_dir, NULL) != 0);
}
XFREE_CLEAR(loghome);
// Invalid $NVIM_LOG_FILE or failed to expand; fall back to default.

View File

@ -3442,7 +3442,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
} else if (!*found_existing_dir && **dirp == NUL) {
int ret;
char *failed_dir;
if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir)) != 0) {
if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir, NULL)) != 0) {
semsg(_("E303: Unable to create directory \"%s\" for swap file, "
"recovery impossible: %s"),
failed_dir, os_strerror(ret));

View File

@ -937,10 +937,13 @@ int os_mkdir(const char *path, int32_t mode)
/// the name of the directory which os_mkdir_recurse
/// failed to create. I.e. it will contain dir or any
/// of the higher level directories.
/// @param[out] created Set to the full name of the first created directory.
/// It will be NULL until that happens.
///
/// @return `0` for success, libuv error code for failure.
int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_dir)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_dir,
char **const created)
FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT
{
// Get end of directory name in "dir".
// We're done when it's "/" or "c:/".
@ -975,6 +978,8 @@ int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_di
if ((ret = os_mkdir(curdir, mode)) != 0) {
*failed_dir = curdir;
return ret;
} else if (created != NULL && *created == NULL) {
*created = FullName_save(curdir, false);
}
}
xfree(curdir);
@ -1002,7 +1007,7 @@ int os_file_mkdir(char *fname, int32_t mode)
*tail = NUL;
int r;
char *failed_dir;
if (((r = os_mkdir_recurse(fname, mode, &failed_dir)) < 0)) {
if (((r = os_mkdir_recurse(fname, mode, &failed_dir, NULL)) < 0)) {
semsg(_(e_mkdir), failed_dir, os_strerror(r));
xfree(failed_dir);
}

View File

@ -3042,7 +3042,7 @@ shada_write_file_nomerge: {}
if (!os_isdir(fname)) {
int ret;
char *failed_dir;
if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir)) != 0) {
if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir, NULL)) != 0) {
semsg(_(SERR "Failed to create directory %s "
"for writing ShaDa file: %s"),
failed_dir, os_strerror(ret));

View File

@ -705,7 +705,7 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading)
// Last directory in the list does not exist, create it.
int ret;
char *failed_dir;
if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir)) != 0) {
if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir, NULL)) != 0) {
semsg(_("E5003: Unable to create directory \"%s\" for undo file: %s"),
failed_dir, os_strerror(ret));
xfree(failed_dir);

View File

@ -30,9 +30,9 @@ func Test_set_filename_other_window()
CheckFunction test_autochdir
let cwd = getcwd()
call test_autochdir()
call mkdir('Xa')
call mkdir('Xb')
call mkdir('Xc')
call mkdir('Xa', 'R')
call mkdir('Xb', 'R')
call mkdir('Xc', 'R')
try
args Xa/aaa.txt Xb/bbb.txt
set acd
@ -44,9 +44,6 @@ func Test_set_filename_other_window()
finally
set noacd
call chdir(cwd)
call delete('Xa', 'rf')
call delete('Xb', 'rf')
call delete('Xc', 'rf')
bwipe! aaa.txt
bwipe! bbb.txt
bwipe! ccc.txt
@ -59,10 +56,10 @@ func Test_acd_win_execute()
set acd
call test_autochdir()
call mkdir('Xfile')
call mkdir('XacdDir', 'R')
let winid = win_getid()
new Xfile/file
call assert_match('testdir.Xfile$', getcwd())
new XacdDir/file
call assert_match('testdir.XacdDir$', getcwd())
cd ..
call assert_match('testdir$', getcwd())
call win_execute(winid, 'echo')
@ -71,7 +68,6 @@ func Test_acd_win_execute()
bwipe!
set noacd
call chdir(cwd)
call delete('Xfile', 'rf')
endfunc
func Test_verbose_pwd()
@ -82,7 +78,7 @@ func Test_verbose_pwd()
edit global.txt
call assert_match('\[global\].*testdir$', execute('verbose pwd'))
call mkdir('Xautodir')
call mkdir('Xautodir', 'R')
split Xautodir/local.txt
lcd Xautodir
call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd'))
@ -116,7 +112,6 @@ func Test_verbose_pwd()
bwipe!
call chdir(cwd)
call delete('Xautodir', 'rf')
endfunc
func Test_multibyte()

View File

@ -918,14 +918,13 @@ func Test_BufEnter()
call assert_equal('++', g:val)
" Also get BufEnter when editing a directory
call mkdir('Xdir')
split Xdir
call mkdir('Xbufenterdir', 'D')
split Xbufenterdir
call assert_equal('+++', g:val)
" On MS-Windows we can't edit the directory, make sure we wipe the right
" buffer.
bwipe! Xdir
call delete('Xdir', 'd')
bwipe! Xbufenterdir
au! BufEnter
" Editing a "nofile" buffer doesn't read the file but does trigger BufEnter
@ -2186,11 +2185,10 @@ func Test_BufWriteCmd()
new
file Xbufwritecmd
set buftype=acwrite
call mkdir('Xbufwritecmd')
call mkdir('Xbufwritecmd', 'D')
write
" BufWriteCmd should be triggered even if a directory has the same name
call assert_equal(1, g:written)
call delete('Xbufwritecmd', 'd')
unlet g:written
au! BufWriteCmd
bwipe!
@ -2947,16 +2945,15 @@ func Test_throw_in_BufWritePre()
endfunc
func Test_autocmd_in_try_block()
call mkdir('Xdir')
call mkdir('Xintrydir', 'R')
au BufEnter * let g:fname = expand('%')
try
edit Xdir/
edit Xintrydir/
endtry
call assert_match('Xdir', g:fname)
call assert_match('Xintrydir', g:fname)
unlet g:fname
au! BufEnter
call delete('Xdir', 'rf')
endfunc
func Test_autocmd_CmdWinEnter()

View File

@ -36,12 +36,70 @@ func Test_mkdir_p()
endtry
" 'p' doesn't suppress real errors
call writefile([], 'Xfile')
call assert_fails('call mkdir("Xfile", "p")', 'E739')
call assert_fails('call mkdir("Xfile", "p")', 'E739:')
call delete('Xfile')
call delete('Xmkdir', 'rf')
call assert_equal(0, mkdir(v:_null_string))
call assert_fails('call mkdir([])', 'E730')
call assert_fails('call mkdir("abc", [], [])', 'E745')
call assert_fails('call mkdir([])', 'E730:')
call assert_fails('call mkdir("abc", [], [])', 'E745:')
endfunc
func DoMkdirDel(name)
call mkdir(a:name, 'pD')
call assert_true(isdirectory(a:name))
endfunc
func DoMkdirDelAddFile(name)
call mkdir(a:name, 'pD')
call assert_true(isdirectory(a:name))
call writefile(['text'], a:name .. '/file')
endfunc
func DoMkdirDelRec(name)
call mkdir(a:name, 'pR')
call assert_true(isdirectory(a:name))
endfunc
func DoMkdirDelRecAddFile(name)
call mkdir(a:name, 'pR')
call assert_true(isdirectory(a:name))
call writefile(['text'], a:name .. '/file')
endfunc
func Test_mkdir_defer_del()
" Xtopdir/tmp is created thus deleted, not Xtopdir itself
call mkdir('Xtopdir', 'R')
call DoMkdirDel('Xtopdir/tmp')
call assert_true(isdirectory('Xtopdir'))
call assert_false(isdirectory('Xtopdir/tmp'))
" Deletion fails because "tmp" contains "sub"
call DoMkdirDel('Xtopdir/tmp/sub')
call assert_true(isdirectory('Xtopdir'))
call assert_true(isdirectory('Xtopdir/tmp'))
call delete('Xtopdir/tmp', 'rf')
" Deletion fails because "tmp" contains "file"
call DoMkdirDelAddFile('Xtopdir/tmp')
call assert_true(isdirectory('Xtopdir'))
call assert_true(isdirectory('Xtopdir/tmp'))
call assert_true(filereadable('Xtopdir/tmp/file'))
call delete('Xtopdir/tmp', 'rf')
" Xtopdir/tmp is created thus deleted, not Xtopdir itself
call DoMkdirDelRec('Xtopdir/tmp')
call assert_true(isdirectory('Xtopdir'))
call assert_false(isdirectory('Xtopdir/tmp'))
" Deletion works even though "tmp" contains "sub"
call DoMkdirDelRec('Xtopdir/tmp/sub')
call assert_true(isdirectory('Xtopdir'))
call assert_false(isdirectory('Xtopdir/tmp'))
" Deletion works even though "tmp" contains "file"
call DoMkdirDelRecAddFile('Xtopdir/tmp')
call assert_true(isdirectory('Xtopdir'))
call assert_false(isdirectory('Xtopdir/tmp'))
endfunc
func Test_line_continuation()

View File

@ -956,6 +956,19 @@ func Test_write_with_deferred_delete()
" call assert_equal('', glob('XdefdeferDelete'))
endfunc
func DoWriteFile()
call writefile(['text'], 'Xthefile', 'D')
cd ..
endfunc
func Test_write_defer_delete_chdir()
let dir = getcwd()
call DoWriteFile()
call assert_notequal(dir, getcwd())
call chdir(dir)
call assert_equal('', glob('Xthefile'))
endfunc
" Check that buffer is written before triggering QuitPre
func Test_wq_quitpre_autocommand()
edit Xsomefile

View File

@ -748,7 +748,7 @@ describe('fs.c', function()
local function os_mkdir_recurse(path, mode)
local failed_str = ffi.new('char *[1]', {nil})
local ret = fs.os_mkdir_recurse(path, mode, failed_str)
local ret = fs.os_mkdir_recurse(path, mode, failed_str, nil)
local str = failed_str[0]
if str ~= nil then
str = ffi.string(str)