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.