0

I've seen the post How do I control how Emacs makes backup files? And a few similar post showing very similar solutions. I'm well aware of this approach. But it doesn't do quite what I would like it to do. I would like each file that I'm going to back up have its own personalized backup directory.

For example, let's say I have the following files in the current directory, /Users/me/project_a/

apple.txt
banana.txt
coconut.txt

when I edit these files, I would like them to have their backups stored in directories as follows:

/Users/me/project_a/.backups/apple.txt/
/Users/me/project_a/.backups/banana.txt/
/Users/me/project_a/.backups/coconut.txt/

If I another project directory, say /Users/me/project_b/, with files

needle.doc
thread.doc
thimble.doc

Then, their respective backups should be located as

/Users/me/project_b/.backups/needle.doc/
/Users/me/project_b/.backups/thread.doc/
/Users/me/project_b/.backups/thimble.doc/

Yes, I'm using the name of the file being backed up as part of the path name for the directory into which it's been saved. So, if I have three previous versions of thimble.doc, the full path name for the backups would be:

/Users/me/project_b/.backups/thimble.doc/thimble.doc.~1~
/Users/me/project_b/.backups/thimble.doc/thimble.doc.~2~
/Users/me/project_b/.backups/thimble.doc/thimble.doc.~3~

I have a work around to accomplish that (see below). Ideally, I would name the backups as:

/Users/me/project_b/.backups/thimble.doc/bak.~1~
/Users/me/project_b/.backups/thimble.doc/bak.~2~
/Users/me/project_b/.backups/thimble.doc/bak.~3~

I haven't yet figured out how to get there. (Any suggestions?)

Here's how I'm able to accomplish the less ideal version of this using the .dir-locals.el file with the following code for saving .txt and .tex files.

  (let (a b)
    (dolist (ae-fh (directory-files-recursively "." "\\.\\(txt\\|tex\\)$"))
      (setq a (file-name-nondirectory ae-fh))
      (setq a (replace-regexp-in-string "\\." "\\\\." a))
      (setq b (concat "./.backups/" (file-name-nondirectory ae-fh)))
      (add-to-list 'backup-directory-alist (cons a b))             
      ))

I've already made backup-directory-alist a buffer-local variable.

This accomplishes what I want, but I don't really like this approach. I would like to altogether avoid using .dir-locals.el as a solution to this problem. But also, I would like to avoid junking up the backup-directory-alist. It would be nice if there were a hook I could apply to the backup process which would inform emacs on the fly how it should name the backup file.

Does anyone know how to do this or know where to point me? I've considered trying to redefine make-backup-file-name-function, but I don't entirely understand what I'm doing with the elisp and right now I've got projects that need to be worked on.

What can folks tell me?

A.Ellett
  • 331
  • 2
  • 10

1 Answers1

0

I have a partial solution.

  • It avoids using .dir-locals.el as I want.
  • It somewhat creates the path on the fly as I want too.
  • It still uses backup-directory-alist

I add the following to my emacs init file:

 (add-hook 'after-change-major-mode-hook 
           '(lambda ()
              (let (a b)
                (if (stringp (buffer-file-name))
                (progn
                  (setq a (file-name-nondirectory (buffer-file-name)))
                  (setq a (replace-regexp-in-string "\\." "\\\\." a))
                  (setq b (concat "./.backups/" (file-name-nondirectory (buffer-file-name))))
                  (add-to-list 'backup-directory-alist (cons a b))             
                      )
                  )
                )))

I don't quite know enough about this hook. So, I'm not sure that this will always work. And, I should probably consider whether there might be files for which this convention really is undesirable.

The (stringp (buffer-file-name)) is minimally necessary in the case of buffers for which there is no underlying file: such as *scratch* or even the mini-buffers.

I am still interested in what solutions others might come up with. Ideally, the solution would be more like

/Users/me/project_b/.backups/thimble.doc/bak.~1~
/Users/me/project_b/.backups/thimble.doc/bak.~2~
/Users/me/project_b/.backups/thimble.doc/bak.~3~

than

/Users/me/project_b/.backups/thimble.doc/thimble.doc.~1~
/Users/me/project_b/.backups/thimble.doc/thimble.doc.~2~
/Users/me/project_b/.backups/thimble.doc/thimble.doc.~3~

UPDATE: Persistence will get you places!!!!

I finally did a google search on: "emacs how do i redefine make-backup-file-name". I found this description of an approach which I adapted to my situation. Namely, I did the following:

(defun z:backup:truncate.backup.name (file)
  (concat (file-name-directory file) "bak"))
(advice-add 'make-backup-file-name-1 :filter-return #'z:backup:truncate.backup.name)

I'm not entirely sure of what everything is about.

  • I don't know what the #' syntax does.
  • I'm not sure what :filter-return does, though I can guess.

I should point out that I still need mae add-hook for 'after-change-major-mode-hook. Without it, I get errors. Apparently, I just can't write the advice function as

(defun z:backup:truncate.backup.name (file)
  (concat (file-name-directory file) (file-name-nondirectory) "/bak"))

I tried it and got an error about not being able to back things up. And I think the reason for this is that backup-directory-alist is used to determine the directory path. I've got some ideas for some tweaks here. But this is where things stand currently.

UPDATE: no more need for my add-hook to after-change-major-mode-hook

I've gotten rid of the issue with the backup directory alist not necessarily being set for the correct directory. This is my final form for z:backup:truncate.backup.name

(defun z:backup:truncate.backup.name ()
  (let* (
         (dir  (file-name-directory (buffer-file-name)))
         (fh   (file-name-nondirectory (buffer-file-name)))
         (qdir (concat dir ".backups/" fh))
          )
    (if (not (file-directory-p qdir))
        (progn
          (if (file-directory-p dir)
              (progn
                (make-directory qdir)
                ))))
  (concat qdir "/bak")))

So now more having to tweak backup-directory-alist. I've accomplished everything I wanted. YEAH.

A.Ellett
  • 331
  • 2
  • 10