1
votes

I have files in some location:

location_a/doc.tex
location_a/doc.cls
...

I want to work on them in another directory via symbolic links:

work_directory/doc.tex -> location_a/doc.tex
work_directory/doc.cls -> location_a/doc.cls
work_directory/doc.pdf
work_directory/doc.log
...

However, when I run emacs doc.tex in the work directory and do some editing, emacs creates a backup file at location_a/doc.tex~. I want the backup file to be stored in the work directory, though. I don't want any new files created in location_a.

How can I make emacs do that?

2

2 Answers

3
votes

This is trickier than it seems it should be because backup-buffer insists on chasing the links of the buffer file name before calling any backup file name construction machinery, such as make-backup-file-name-function. The result is that Emacs allows no way to customize this behavior, short of redefining backup-buffer, which is a fairly complicated piece of code.

A compromise solution I came up with is to install an "advice" around backup-buffer that temporarily disables file-chase-links while backup-buffer is being evaluated. This allows the backup file to be in the directory where the symlink resides. However, it also causes Emacs to create the backup by renaming the original symlink, leaving one with work_directory/doc.tex~ being a symlink that points to location_a/doc.tex! Fortunately, this is easy to prevent by setting backup-by-copying to t.

Here is the code. A word of warning: while I have tried it to verify that it works, I cannot guarantee that it will not have an undesirable side effect, like the above interference with the backup mechanism that required backup-by-copying. However, it might also work just fine - just be careful when using it.

(require 'cl)  ; for flet
(defadvice backup-buffer (around disable-chase-links)
  (flet ((file-chase-links (file) file))
    ad-do-it))
(ad-activate 'backup-buffer)
1
votes

For the fun of it, let me describe a radically different approach, based on directory variables.

In short, you would put in your work-directory/ a file named .dir-locals.el containing:

((nil . ((eval . (set (make-local-variable 'backup-directory-alist)
                      (list (cons "."
                                  (file-relative-name
                                   (file-name-directory (buffer-file-name))
                                   (file-name-directory (file-truename
                                                         (buffer-file-name)))))))))))

What this does is abusing somewhat the backup-directory-alist, and install a local version of it for all your files in work-directory/. That local version will in turn make sure that any backup file is kept within work-directory.

In order to achieve that, we need 2 things:

  • have something like '(("." . "path/to/work-directory/")) as the local value
  • make sure this path is relative to location_a/

The reason for the second point is that as noted elsewhere, the starting point of backup-buffer is indeed the location of the actual file, once symlinks are resolved. And we can't simply put the absolute path without having backup files changing shape (in case of absolute path for the backup directory, the backup filenames encode the complete path, so that there is no collision)

Notes:

  • you'll need to make sure that specific local variable is recorded in the safe-local-variable-values. Since it's a generic form, it's a one time job though (just hit "!" the first time you're asked about it)
  • this assumes find-file-visit-truename is set to nil, but I guess you wouldn't ask that question if that was not the case :)

Pros of the approach:

  • no need for advice (which is always a good thing)
  • reasonably portable although it assumes your Emacs supports directory variables
  • you keep the flexibility to put that in place only where you need it

Cons of the approach:

  • well, obviously you might have to copy that .dir-locals.el in several places

Also note that if you wanted a one-shot approach, you could make it much simpler, such as:

((nil . ((backup-directory-alist (("." . "../path/to/work-directory"))))))

where you actually compute the relative name yourself, once and for all.