4

Is it possible to customize Emacs so that whenever I save a file, Emacs also copies it to a specific folder?

This directory could be, for instance, D:\my_backups or C:\Users\username\Dropbox\my_backups.

  • The copy should have the same name as the original file (no appended ~).
  • If the backup already exists, overwrite without asking.
  • I want to backup the same version that is being saved, not a previous version.
6
  • 1
    You might consider using the after-save-hook for this purpose. However, I would recommend: (1) use Dropbox as your main database / repository; (2) symlink your main database to Dropbox; or, (3) use a synchronization utility as needed (e.g., once a day, or after any major work is done). Here is a link regarding how to use rsync to create a mirror -- it is a one-way synchronization utility: emacs.stackexchange.com/a/5844/2287 Another user in that same thread talks about using unison, which is a two-way synchronization utility.
    – lawlist
    Dec 31, 2014 at 22:56
  • @lawlist I am looking for a solution within Emacs. I would prefer to have my files on my local drive and only use Dropbox as a backup and not vice-versa. Using symlinks is a little bit complicated and I have do it for each folder. Moreover if I work with a TeX file, it would be desirable to just backup the main TeX file, not many other files which are generated by TeX engines. These are the reasons that I am looking for a solution within Emacs.
    – Name
    Jan 1, 2015 at 6:45
  • @lawlist I would be grateful if it would be possible to elaborate more about using rsync or save-after-hook. If there is a solution to address my question using rsync or save-after-hook, it would be perfect.
    – Name
    Jan 1, 2015 at 6:56
  • The easiest solution is to symlink your database to Dropbox, and everything happens automatically -- it synchronizes any changes almost immediately. rsync is either run manually or set on a timer (e.g, a nightly backup). I am not familiar enough with Windows to create symlinks and so forth; however, I have done that with OSX and it's been working fine for the past year. My database is really too important to rely solely on Emacs for synchronization, because I'm constantly customizing Emacs (i.e., break/fix). rsync is a rock solid backup utility, and the link I provided has an example.
    – lawlist
    Jan 1, 2015 at 7:39
  • 1
    You want shadowfile.el, which is part of Emacs. It's worth a longer answer, but my kid just woke up. "This package helps you to keep identical copies of files in more than one ;; place - possibly on different machines. When you save a file, it checks ;; whether it is on the list of files with "shadows", and if so, it tries to ;; copy it when you exit Emacs (or use the shadow-copy-files command)." Jan 1, 2015 at 21:43

2 Answers 2

4

Here is a very rudimentary example (tested on OSX, and may work on other Unix flavor systems). It is possible to write an entire library surrounding this concept, and this example is certainly not meant to be an all inclusive solution -- it is just an idea put on paper so to speak:

(defvar backup-repo (directory-file-name "/private/tmp")
"NOTE:  `directory-file-name` makes it so that the user may either omit, or
include the forward trailing slash at the end of the `backkup-repo` path.")

(defun after-save-hook-fn ()
  (let* (
      (backup-repo-filename
        (when buffer-file-name (concat backup-repo buffer-file-name)))
      (dir (when backup-repo-filename (file-name-directory backup-repo-filename))) )
    (when buffer-file-name
      (when (not (file-exists-p dir))
         (make-directory dir t))
      (write-region (point-min) (point-max) backup-repo-filename))))

(add-hook 'after-save-hook 'after-save-hook-fn)

Here is an example using Emacs on Windows XP:

EDIT (January 5, 2015):  Added feature to optionally preserve the drive letter as a folder sandwiched between the backup-repo and the basename of the file being saved -- the variable that controls that optional behavior is called drive-letter-as-folder.

(defvar backup-repo
  (file-name-as-directory "c:/Documents and Settings/Administrator/Desktop/tmp")
  "NOTE:  `file-name-as-directory` makes it so that the user may either omit, or
  include the forward trailing slash at the end of the `backup-repo` path.")

(defvar drive-letter-as-folder t
"Setting this variable to `t` will cause the function `after-save-hook-fn` to
create a folder with the driver letter immediately following the `backup-repo`
directory -- e.g., c:/Documents and Settings/Administrator/Desktop/tmp and drive
`e:` will become c:/Documents and Settings/Administrator/Desktop/tmp/e/.")

(defun after-save-hook-fn ()
  (let* (
      (basename
        (when buffer-file-name (split-string buffer-file-name ":/")))
      (base-filename
        (when buffer-file-name
          (cond
            ((null drive-letter-as-folder)
              (car (cdr basename)))
            (drive-letter-as-folder
              (concat (car basename) "/" (car (cdr basename)))))))
      (backup-repo-filename
        (when buffer-file-name (concat backup-repo base-filename))) 
      (dir (when backup-repo-filename (file-name-directory backup-repo-filename))) )
    (when buffer-file-name
      (when (not (file-exists-p dir))
         (make-directory dir t))
      (copy-file buffer-file-name backup-repo-filename t))))

(add-hook 'after-save-hook 'after-save-hook-fn)

The function write-region has additional arguments that can be used -- here is a copy / paste of the doc-string:

write-region is an interactive built-in function in `C source code'.

(write-region START END FILENAME &optional APPEND VISIT LOCKNAME MUSTBENEW)

Write current region into specified file.
When called from a program, requires three arguments:
START, END and FILENAME.  START and END are normally buffer positions
specifying the part of the buffer to write.
If START is nil, that means to use the entire buffer contents.
If START is a string, then output that string to the file
instead of any buffer contents; END is ignored.

Optional fourth argument APPEND if non-nil means
  append to existing file contents (if any).  If it is a number,
  seek to that offset in the file before writing.
Optional fifth argument VISIT, if t or a string, means
  set the last-save-file-modtime of buffer to this file's modtime
  and mark buffer not modified.
If VISIT is a string, it is a second file name;
  the output goes to FILENAME, but the buffer is marked as visiting VISIT.
  VISIT is also the file name to lock and unlock for clash detection.
If VISIT is neither t nor nil nor a string,
  that means do not display the "Wrote file" message.
The optional sixth arg LOCKNAME, if non-nil, specifies the name to
  use for locking and unlocking, overriding FILENAME and VISIT.
The optional seventh arg MUSTBENEW, if non-nil, insists on a check
  for an existing file with the same name.  If MUSTBENEW is `excl',
  that means to get an error if the file already exists; never overwrite.
  If MUSTBENEW is neither nil nor `excl', that means ask for
  confirmation before overwriting, but do go ahead and overwrite the file
  if the user confirms.

This does code conversion according to the value of
`coding-system-for-write', `buffer-file-coding-system', or
`file-coding-system-alist', and sets the variable
`last-coding-system-used' to the coding system actually used.

This calls `write-region-annotate-functions' at the start, and
`write-region-post-annotation-function' at the end.

The function copy-file could also be used and there is an argument for overwriting without confirmation:

copy-file is an interactive built-in function in `C source code'.

(copy-file FILE NEWNAME &optional OK-IF-ALREADY-EXISTS KEEP-TIME
PRESERVE-UID-GID PRESERVE-PERMISSIONS)

Copy FILE to NEWNAME.  Both args must be strings.
If NEWNAME names a directory, copy FILE there.

This function always sets the file modes of the output file to match
the input file.

The optional third argument OK-IF-ALREADY-EXISTS specifies what to do
if file NEWNAME already exists.  If OK-IF-ALREADY-EXISTS is nil, we
signal a `file-already-exists' error without overwriting.  If
OK-IF-ALREADY-EXISTS is a number, we request confirmation from the user
about overwriting; this is what happens in interactive use with M-x.
Any other value for OK-IF-ALREADY-EXISTS means to overwrite the
existing file.

Fourth arg KEEP-TIME non-nil means give the output file the same
last-modified time as the old one.  (This works on only some systems.)

A prefix arg makes KEEP-TIME non-nil.

If PRESERVE-UID-GID is non-nil, we try to transfer the
uid and gid of FILE to NEWNAME.

If PRESERVE-PERMISSIONS is non-nil, copy permissions of FILE to NEWNAME;
this includes the file modes, along with ACL entries and SELinux
context if present.  Otherwise, if NEWNAME is created its file
permission bits are those of FILE, masked by the default file
permissions.
2
  • Please feel free to put your new edit as a separate answer in order that I can acknowledge your help within site.
    – Name
    Jan 5, 2015 at 18:14
  • There may be additional bug fixes and so forth as time goes by -- for example, I just made a small fix that affected the original behavior.
    – lawlist
    Jan 5, 2015 at 18:17
5

Emacs has a built-in backup mechanism, which is what makes all the "~" files Emacs likes to leave around anywhere. You can tell Emacs to put all the backup files in a single directory with

(setq backup-directory-alist (list '("." . "/path/to/backups")))

backup-directory-alist is a list of pairs (PATTERN . PATH) where PATTERN is a regexp matching the file name and PATH is where the matching files will be backed up. The "." pattern matches everything, but you might want to use something for specific (like "\.txt\'" to for ".txt" files) unless you really want every file you edit going into Dropbox

The default behavior is to preserve the original name and path of the file. So /path/to/file.txt is copied to /path/to/backups/!path!to!file.txt~

You also might want to set vc-make-backup-files to t so that files under version control also get backed up.

In Emacs 24.4 or later, the names of the backup file come from the function stored in make-backup-file-name-function, which we can change to remove the "~". This function (which hasn't been tested) just returns whatever the default function does the a "~" stripped off of the end.

(defun make-backup-file--no-tilde (filename)
  (replace-regexp-in-string
   "~$" ""
   (make-backup-file-name--default-function filename)
   )
  )

(setq make-backup-file-name-function #'make-backup-file--no-tilde)

In earlier Emacs, we can advise make-backup-file-name to strip the "~"

(defadvice make-backup-file-name (around make-file-name-no-tilde activate)
  (setq ad-return-value (replace-regexp-in-string "~$" "" ad-do-it))
  )

This could be modified to remove the path as well, but then /a/b/file.txt and /c/d/file.txt would backup to the same file.

3
  • I haven't experimented with the auto-save-file-name-transforms variable, but here is a link to some limited documentation: gnu.org/software/emacs/manual/html_node/emacs/… Perhaps that would be an option to somewhat control the back file name?
    – lawlist
    Dec 31, 2014 at 23:56
  • Thank you again, it seems that you code is fine only for certain files. For example for TeX files, it seems that it doesn't work (at least for me).
    – Name
    Jan 1, 2015 at 6:30
  • Also the backup mechanism usually save a prior version of the file not the last version.
    – Name
    Jan 1, 2015 at 7:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.