backups-mode for emacs
The inspiration for this came after reading John Siracusa's review of the Document Model new to Mac OS X Lion. This new framework on the Mac allows for the following:
- All documents are automatically saved. This includes when the user closes the document or quits the application.
- Old versions of the document are automatically stored and accessible.
- The user has the ability to manually save a verion of the document.
- Old versions can be viewed, diffed, and reverted.
- Reverting an old version saves the current file as a version and copies the selected version to become the current file.
So I set out to emulate these features in emacs. emacs already has its own rudimentary version control system that can be turned on simply by configuring emacs. It can also be configured to keep those backup files tucked away in a central directory. It can also be configured to automatically save (without prompting) your files when you kill the buffer or quit emacs. In the event that emacs crashes, you can also restore your file from an autosave file emacs creates for you. So it has all of that out of the box.
What it doesn't have (or at least I couldn't find) is the ability to easily find, view, diff, and revert those versioned backup files. That is where backups-mode.el comes into play. While editing any file-based buffer in emacs, you can now do two extra things. You can list all backups and you can explicily save a version.
Using the emacs version control functionality should not be a replacement for a proper version control system such as cvs, svn, git, mercurial, and the like. So if you are developing a project or typing a manuscript, you'll want to use one of those systems. I find this useful for the less important stuff such as blog posts or exploritory programming.
git clone email@example.com:chadbraunduin/backups-mode.git cd backups-mode # copy the necessary files to your emacs load-path cp backups-mode.el bm-utilities.el backup-walker.el ~/.emacs.d/ # this assumes ~/.emacs.d/ is in your emacs load-path # add the following to .emacs (require 'backups-mode) (backups-minor-mode)
;; putting this in your .emacs will allow you to change version control settings. The following are the default settings found in backups-mode.el. (setq backup-by-copying t delete-old-versions t kept-new-versions 6 kept-old-versions 2 version-control t)
emacs documentation will explain these options.
If you are using Windows, you'll want to set these objects to be Windows specific:
By default, backups are saved to "
/.emacs.d/backups" and tramp file backups are saved to "/.emacs.d/tramp-backups". These defaults can be changed by setting:
The backups-minor-mode keymap is defined in variable named backups-minor-mode-keymap. So you can make individual key changes like so:
(define-key backups-minor-mode-keymap (kbd "C-c b") nil) (define-key backups-minor-mode-keymap (kbd "C-c l") 'list-backups)
Or redifine the entire keymap like so:
(setq backups-minor-mode-keymap (let ((map (make-sparse-keymap))) (define-key map (kbd "C-c sv") 'save-version) (define-key map (kbd "C-c lb") 'list-backups) (define-key map (kbd "C-c kb") 'kill-buffer-prompt) (when (backup-walker-p) (define-key map (kbd "C-c bw") 'backup-walker-start)) map))
As an example, here's the configuration from my .emacs file.
(require 'backups-mode) (defvar backup-directory "~/.emacs-backups/backups/") (defvar tramp-backup-directory "~/.emacs-backups/tramp-backups/") (backups-minor-mode) ;; keep all versions forever (setq delete-old-versions 1)
While editing any file-based emacs buffer, there are some additional commands:
This will version the previous saved copy of the file.
By default, this command can be done with control-c v ("\C-cv")
This will open a new buffer in backups-mode which will list all backups of the file.
By default, this command can be done with control-c b ("\C-cb")
This will open a new buffer in backup-walker mode which will let you sequentially move through backup diffs.
By default, this command can be done with control-c w ("\C-cw")
This will allow the user to close a buffer without saving any changes
By default, this command can be done with control-c k ("\C-ck")
While in the backups-mode buffer, these are the commands:
This is done by aligning the cursor to a file's line, and hitting [enter].
Backup files will be opened read-only.
This is done by aligning the cursor to a file's line, and typing "R".
Reverting will save the current file as a version, then replace the current file with the chosen backup.
Diff 2 Files
This is done by aligning the cursor to a file's line, and typing "d". This will mark that line as first file to diff.
Then, you align the cursor to another file's line and type "d". This will run the "diff-function" command on the two selected files.
Purge (delete) Backups
This is similar to Dired mode's batch delete, except Backups Mode uses "p" instead of "d" to mark backups for deletion. The choice of letters is because "d" is already being used to diff backups.
After marking one-to-many backups, "x" deletes all the marked backups.
I've adapted lewang's backup-walker project. Backup Walker gives you a different view on your backups. Whereas Backups Mode lists all of your backups, Backup Walker starts with a diff of the current file and the previous backup. You can then sequentially move through diffs of consecutive backup files. Often, if you know what you are looking for, Backup Walker can be more efficient than Backups Mode.
My adapted version of backup-walker is found in the "fallback" directory. Backups-mode will only use that version if it cannot find another version of backup-walker in your load-path. So a more up-to-date version will override what I have saved as a fallback.
The problem with creating N backup files per file is that over time you'll have generated a lot of backup files. Some of these backup files may even be orphaned if the original file is moved or deleted. I've taken two approaches for this problem:
For local files
For local files, I've created the script "show-orphaned.sh" (found in the scripts directory). It goes through my backups directory and displays all orphaned backups. I've created a @daily crontab job to remove all of the orphaned backups.
@daily /home/chadbraunduin/.emacs-backups/backups/show-orphaned.sh | xargs -r /bin/rm -f
For tramp files
For tramp files, we cannot assume to be able to access the original file. Therefore, I've taken a more crude approach with tramp backups. I've scheduled a @daily crontab job that removes any tramp backups that have not be accessed in the past 180 days (roughly 6 months).
@daily find /home/chadbraunduin/.emacs-backups/tramp-backups/ -type f -name "*.*~" -atime +180 | xargs -r /bin/rm -f
I use rsnapshot for rsync backups to an external drive. I've decided I do not care to backup these emacs generated backup files. Therefore, I've added these two lines to /etc/rsnapshot.conf:
exclude /home/chadbraunduin/.emacs-backups/backups/*.*~ exclude /home/chadbraunduin/.emacs-backups/tramp-backups/*.*~
Bugs and TODOs
As with most projects, this is still a work in progress. The known issues are:
- I am planning on adapting this project to work with git-mode work-in-progress backups (WIP).
- A bug using this on Mac Os X has been reported. This is related to use the function copy-file.
- I have not tested it in Windows, yet. I believe configuration changes will be necessary to make it work in that environment. Since I only use emacs in Linux, this is not a personal priority for me.