aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/options.txt22
-rw-r--r--src/nvim/fileio.c51
-rw-r--r--src/nvim/memline.c2
-rw-r--r--src/nvim/testdir/test_alot.vim1
-rw-r--r--src/nvim/testdir/test_backup.vim58
-rw-r--r--test/functional/core/fileio_spec.lua25
6 files changed, 142 insertions, 17 deletions
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 270232179a..386fcdf8c0 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -843,6 +843,14 @@ A jump table for the options with a short description can be found at |Q_op|.
name, precede it with a backslash.
- To include a comma in a directory name precede it with a backslash.
- A directory name may end in an '/'.
+ - For Unix and Win32, if a directory ends in two path separators "//",
+ the swap file name will be built from the complete path to the file
+ with all path separators changed to percent '%' signs. This will
+ ensure file name uniqueness in the backup directory.
+ On Win32, it is also possible to end with "\\". However, When a
+ separating comma is following, you must use "//", since "\\" will
+ include the comma in the file name. Therefore it is recommended to
+ use '//', instead of '\\'.
- Environment variables are expanded |:set_env|.
- Careful with '\' characters, type one before a space, type two to
get one in the option (see |option-backslash|), for example: >
@@ -1992,12 +2000,14 @@ A jump table for the options with a short description can be found at |Q_op|.
- A directory starting with "./" (or ".\" for Windows) means to
put the swap file relative to where the edited file is. The leading
"." is replaced with the path name of the edited file.
- - For Unix and Win32, if a directory ends in two path separators "//"
- or "\\", the swap file name will be built from the complete path to
- the file with all path separators substituted to percent '%' signs.
- This will ensure file name uniqueness in the preserve directory.
- On Win32, when a separating comma is following, you must use "//",
- since "\\" will include the comma in the file name.
+ - For Unix and Win32, if a directory ends in two path separators "//",
+ the swap file name will be built from the complete path to the file
+ with all path separators substituted to percent '%' signs. This will
+ ensure file name uniqueness in the preserve directory.
+ On Win32, it is also possible to end with "\\". However, When a
+ separating comma is following, you must use "//", since "\\" will
+ include the comma in the file name. Therefore it is recommended to
+ use '//', instead of '\\'.
- Spaces after the comma are ignored, other spaces are considered part
of the directory name. To have a space at the start of a directory
name, precede it with a backslash.
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 58e6b2ae92..fcf15638c7 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -2650,6 +2650,7 @@ buf_write(
*/
if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) {
FileInfo file_info;
+ const bool no_prepend_dot = false;
if ((bkc & BKC_YES) || append) { /* "yes" */
backup_copy = TRUE;
@@ -2737,6 +2738,7 @@ buf_write(
int some_error = false;
char_u *dirp;
char_u *rootname;
+ char_u *p;
/*
* Try to make the backup in each directory in the 'bdir' option.
@@ -2756,6 +2758,17 @@ buf_write(
* Isolate one directory name, using an entry in 'bdir'.
*/
(void)copy_option_part(&dirp, IObuff, IOSIZE, ",");
+ p = IObuff + STRLEN(IObuff);
+ if (after_pathsep((char *)IObuff, (char *)p) && p[-1] == p[-2]) {
+ // Ends with '//', Use Full path
+ if ((p = (char_u *)make_percent_swname((char *)IObuff, (char *)fname))
+ != NULL) {
+ backup = (char_u *)modname((char *)p, (char *)backup_ext,
+ no_prepend_dot);
+ xfree(p);
+ }
+ }
+
rootname = get_file_in_dir(fname, IObuff);
if (rootname == NULL) {
some_error = TRUE; /* out of memory */
@@ -2764,10 +2777,14 @@ buf_write(
FileInfo file_info_new;
{
- /*
- * Make backup file name.
- */
- backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE);
+ //
+ // Make the backup file name.
+ //
+ if (backup == NULL) {
+ backup = (char_u *)modname((char *)rootname, (char *)backup_ext,
+ no_prepend_dot);
+ }
+
if (backup == NULL) {
xfree(rootname);
some_error = TRUE; /* out of memory */
@@ -2893,12 +2910,26 @@ nobackup:
* Isolate one directory name and make the backup file name.
*/
(void)copy_option_part(&dirp, IObuff, IOSIZE, ",");
- rootname = get_file_in_dir(fname, IObuff);
- if (rootname == NULL)
- backup = NULL;
- else {
- backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE);
- xfree(rootname);
+ p = IObuff + STRLEN(IObuff);
+ if (after_pathsep((char *)IObuff, (char *)p) && p[-1] == p[-2]) {
+ // path ends with '//', use full path
+ if ((p = (char_u *)make_percent_swname((char *)IObuff, (char *)fname))
+ != NULL) {
+ backup = (char_u *)modname((char *)p, (char *)backup_ext,
+ no_prepend_dot);
+ xfree(p);
+ }
+ }
+
+ if (backup == NULL) {
+ rootname = get_file_in_dir(fname, IObuff);
+ if (rootname == NULL) {
+ backup = NULL;
+ } else {
+ backup = (char_u *)modname((char *)rootname, (char *)backup_ext,
+ no_prepend_dot);
+ xfree(rootname);
+ }
}
if (backup != NULL) {
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 05cc62bb33..2824d57f49 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -1437,7 +1437,7 @@ recover_names (
* Append the full path to name with path separators made into percent
* signs, to dir. An unnamed buffer is handled as "" (<currentdir>/"")
*/
-static char *make_percent_swname(const char *dir, char *name)
+char *make_percent_swname(const char *dir, char *name)
FUNC_ATTR_NONNULL_ARG(1)
{
char *d = NULL;
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index f1274b01c8..5668f45dea 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -2,6 +2,7 @@
" This makes testing go faster, since Vim doesn't need to restart.
source test_assign.vim
+source test_backup.vim
source test_behave.vim
source test_cd.vim
source test_changedtick.vim
diff --git a/src/nvim/testdir/test_backup.vim b/src/nvim/testdir/test_backup.vim
new file mode 100644
index 0000000000..fa10430613
--- /dev/null
+++ b/src/nvim/testdir/test_backup.vim
@@ -0,0 +1,58 @@
+" Tests for the backup function
+
+func Test_backup()
+ set backup backupdir=.
+ new
+ call setline(1, ['line1', 'line2'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ let l = readfile('Xbackup.txt~')
+ call assert_equal(['line1', 'line2'], l)
+ bw!
+ set backup&vim backupdir&vim
+ call delete('Xbackup.txt')
+ call delete('Xbackup.txt~')
+endfunc
+
+func Test_backup2()
+ set backup backupdir=.//
+ new
+ call setline(1, ['line1', 'line2', 'line3'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ sp *Xbackup.txt~
+ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
+ let f=expand('%')
+ call assert_match('src%nvim%testdir%Xbackup.txt\~', f)
+ bw!
+ bw!
+ call delete('Xbackup.txt')
+ call delete(f)
+ set backup&vim backupdir&vim
+endfunc
+
+func Test_backup2_backupcopy()
+ set backup backupdir=.// backupcopy=yes
+ new
+ call setline(1, ['line1', 'line2', 'line3'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ sp *Xbackup.txt~
+ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
+ let f=expand('%')
+ call assert_match('src%nvim%testdir%Xbackup.txt\~', f)
+ bw!
+ bw!
+ call delete('Xbackup.txt')
+ call delete(f)
+ set backup&vim backupdir&vim backupcopy&vim
+endfunc
diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua
index e6bce85b8a..f4c476560d 100644
--- a/test/functional/core/fileio_spec.lua
+++ b/test/functional/core/fileio_spec.lua
@@ -9,9 +9,12 @@ local nvim_prog = helpers.nvim_prog
local request = helpers.request
local retry = helpers.retry
local rmdir = helpers.rmdir
+local mkdir = helpers.mkdir
local sleep = helpers.sleep
local read_file = helpers.read_file
local trim = helpers.trim
+local currentdir = helpers.funcs.getcwd
+local iswin = helpers.iswin
describe('fileio', function()
before_each(function()
@@ -24,6 +27,7 @@ describe('fileio', function()
os.remove('Xtest_startup_file2')
os.remove('Xtest_ั‚ะตัั‚.md')
rmdir('Xtest_startup_swapdir')
+ rmdir('Xtest_backupdir')
end)
it('fsync() codepaths #8304', function()
@@ -88,6 +92,27 @@ describe('fileio', function()
eq('foo', bar_contents);
end)
+ it('backup with full path #11214', function()
+ clear()
+ mkdir('Xtest_backupdir')
+ command('set backup')
+ command('set backupdir=Xtest_backupdir//')
+ command('write Xtest_startup_file1')
+ feed('ifoo<esc>')
+ command('write')
+ feed('Abar<esc>')
+ command('write')
+
+ -- Backup filename = fullpath, separators replaced with "%".
+ local backup_file_name = string.gsub(currentdir()..'/Xtest_startup_file1',
+ iswin() and '[:/\\]' or '/', '%%') .. '~'
+ local foo_contents = trim(read_file('Xtest_backupdir/'..backup_file_name))
+ local foobar_contents = trim(read_file('Xtest_startup_file1'))
+
+ eq('foobar', foobar_contents);
+ eq('foo', foo_contents);
+ end)
+
it('readfile() on multibyte filename #10586', function()
clear()
local text = {