16

I'm building a custom vimrc to improve my workflow, and I really liked the idea of setting a centralized directory to keep all backup, swap and undo files together, like this:

" === BACKUP SETTINGS ===
" turn backup ON
set backup
set backupdir=~/.vim/backup//

" === SWAP FILES ===
" turn swap files ON
set swapfile
set directory=~/.vim/swap//

" === UNDO FILES ===
" turn undofiles ON
set undofile
set undodir=~/.vim/undo//

The double trailing slash is supposed to result in a filename expansion, where the resulting backup/swap/undo filename will be the full path, with % replacing every /, something like %home%username%path%to%your%file.ext.

Everything works perfectly for swap and undo files, but backups refuses to work, generating filenames in the format file.ext~ without the full path expansion, which means that when I edit two files with the same name, the backup of the first is lost (overwritten by the second).

Does anyone have a clue about this problem?

UPDATE: this bug is now fixed since Vim 8.1.0251.

Victor Schröder
  • 6,738
  • 2
  • 42
  • 45

5 Answers5

11

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!

Ingo Karkat
  • 167,457
  • 16
  • 250
  • 324
7

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).

Community
  • 1
  • 1
Victor Schröder
  • 6,738
  • 2
  • 42
  • 45
3

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.

tforgione
  • 1,234
  • 15
  • 29
  • 1
    thank you for your suggestion. I tried this, but the main problem is that it saves the new version of the file twice, so we end with two copies of the same thing and lose the old one. The purpose of the backup is to have the previous version saved in the `backupdir`. Take a look on my solution in the answer I just posted. – Victor Schröder Jul 20 '16 at 11:10
  • 1
    Nice one, I see what you mean. However, when I mess up a file, most of the times, I mess it up with something else than vim (git, or `rm`...), so the backup will be the last version of what I saved with vim, and if I mess up something with vim, I still have the `undofile` that can help me to get my backup. – tforgione Jul 20 '16 at 11:43
  • 1
    I understand and see your point. I think it's just a matter of different use cases, both valid. Your script introduces new functionality, because the documentation says that the backup feature should keep the old version. – Victor Schröder Jul 20 '16 at 12:41
3

As of Vim 8.1.0251, using backupdir with // at the end of the path works as expected.

The issue was fixed by commit b782ba475a ("patch 8.1.0251: using full path is not supported for 'backupdir'") on 2018-08-08.

For Neovim users: the issue was fixed on 2019-11-18. Neovim 0.4.4 (released on 2020-08-07) is the first Neovim release to include the fix.

Flux
  • 9,805
  • 5
  • 46
  • 92
1

In addition to the answer of Victor Schröder, I suggest using this autocommand instead :

autocmd BufWritePre * if filereadable(@%) | :call SaveBackups() | endif

Unless this, it won't work in the case you are openning a new file directly with vim. And it doesn't make sense to save an empty backup...

DevOps Craftsman
  • 355
  • 3
  • 16