Why VIM backup filenames are not correct? 'backupdir' option not performing as expected
It seems like the 'backupdir'
option doesn't support the translation of the full absolute path into a filename (using %
for path separators) like 'directory'
and 'undodir'
do. At least nothing is mentioned under :help 'backupdir'
.
Since this is inconsistent, and I see your use case, you should submit a request at the vim_dev mailing list. Actually, there is already such a patch in the (veeery long) patch queue (:help todo.txt
):
7 The 'directory' option supports changing path separators to "%" to make
file names unique, also support this for 'backupdir'. (Mikolaj Machowski)
Patch by Christian Brabandt, 2010 Oct 21.
Please kindly lobby on the vim_dev mailing list to have its priority raised!
Some time already passed and it seems that this bug is not going to be fixed anytime soon. The suggestion presented by @DragonRock is a nice one, but misses a crucial point, which is: the generated backup should be a copy of the current version of the file, BEFORE overwriting. So I used the basic idea of an auto-command, but with a different event BufWritePre
to make a copy of the file to the backup destinations, before committing the changes to disk.
This is the final solution, that has exactly the same behavior as we would expect from the broken feature (only works on Linux):
" === BACKUP SETTINGS ===
" turn backup OFF
" Normally we would want to have it turned on. See bug and workaround below.
" OBS: It's a known-bug that backupdir is not supporting
" the correct double slash filename expansion
" see: https://code.google.com/p/vim/issues/detail?id=179
set nobackup
" set a centralized backup directory
set backupdir=~/.vim/backup//
" This is the workaround for the backup filename expansion problem.
autocmd BufWritePre * :call SaveBackups()
function! SaveBackups()
if expand('%:p') =~ &backupskip | return | endif
" If this is a newly created file, don't try to create a backup
if !filereadable(@%) | return | endif
for l:backupdir in split(&backupdir, ',')
:call SaveBackup(l:backupdir)
endfor
endfunction
function! SaveBackup(backupdir)
let l:filename = expand('%:p')
if a:backupdir =~ '//$'
let l:backup = escape(substitute(l:filename, '/', '%', 'g') . &backupext, '%')
else
let l:backup = escape(expand('%') . &backupext, '%')
endif
let l:backup_path = a:backupdir . l:backup
:silent! execute '!cp ' . resolve(l:filename) . ' ' . l:backup_path
endfunction
Note that the auto-command was extracted to a dedicated function for clarity. Also the set nobackup
is important, because otherwise it would generate duplicated backups (one with the correct name and one with the incorrect name).
It'll skip backups that matches backupskip
as expected, support multiple backup destinations and append the backupext
(the file extension, useful for searching).
It'll also skip if the current saved buffer is brand new (or, in other words, if you are creating a new file using vim directly). It'd have no point trying to create a empty backup file and, actually, it would throw an error because the file is still not there to be copied. Thanks @Yahya for the suggestion!
The silent!
instruction prevents the backup operation to disturb the normal flow of the file save operation by echoing the backup creation or failures (otherwise the save itself could fail).
I add this answer because I find it really frustrating.
This could be a work-around until Vim add the path translation functionnality to backupdir
. What I did is adding the following line to my .vimrc
:
autocmd BufWritePost * :execute ':w! ' ."$HOME/.vim/backups/" . substitute(escape(substitute(expand('%:p'), "/", "%", "g"), "%"), ' ', '\\ ', 'g')
Basically, everytime you save a file, it will also save a copy in $HOME/.vim/backups
.