Mercurial > hg > xemacs-beta
diff lisp/packages/backup-dir.el @ 12:bcdc7deadc19 r19-15b7
Import from CVS: tag r19-15b7
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:48:16 +0200 |
parents | |
children | e45d5e7c476e |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/packages/backup-dir.el Mon Aug 13 08:48:16 2007 +0200 @@ -0,0 +1,449 @@ +;;; BACKUP-DIR.EL: Emacs functions to allow backup files to live in +;;; some other directory(s). Version 2.0 +;;; +;;; Copyright (C) 1992-97 Greg Klanderman +;;; +;;; This program is free software; you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 1, or (at your option) +;;; any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; A copy of the GNU General Public License can be obtained from +;;; the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA +;;; 02139, USA. +;;; +;;; Send bug reports, etc. to greg@alphatech.com or gregk@ai.mit.edu. +;;; +;;; +;;; Modification History +;;; ==================== +;;; +;;; 12/28/1996 Version 2.0 +;;; Updated for XEmacs 19.15b4, much of code reorganized & cleaned up +;;; +;;; 12/27/1996 Version 1.6 +;;; explicit loading of dired replaced to use dired-load-hook +;;; (suggested by Thomas Feuster, feuster@tp4.physik.uni-giessen.de) +;;; +;;; 12/2/1996 Version 1.5 +;;; Took out obsolete byte compiler options +;;; +;;; 9/24/1996 Version 1.4 +;;; Fix some bugs, change to alist OPTIONS list (ok-create, full-path..) from +;;; separate fields for each option variable. Added search-upward option. +;;; Added new function `find-file-latest-backup' to find a file's latest backup. +;;; +;;; 1/26/1996 Version 1.3 +;;; Name change to backup-dir.el +;;; +;;; 3/22/1995 Version 1.2 +;;; Added new definitions for functions `file-newest-backup', `latest-backup-file', +;;; and `diff-latest-backup-file' so various other emacs functions will find the +;;; right backup files. +;;; +;;; 4/23/1993 Version 1.1 +;;; Reworked to allow different behavior for different files based on the +;;; alist `bkup-backup-directory-info'. +;;; +;;; Fall 1992 Version 1.0 +;;; Name change and added ability to make directories absolute. Added the +;;; full path stuff to make backup name unique for absolute directories. +;;; +;;; Spring 1992 Version 0.0 +;;; Original +;;; +;;; +;;; Description: +;;; ============ +;;; +;;; Allows backup files to be optionally stored in some directories, based on +;;; the value of the alist, `bkup-backup-directory-info'. This variable is a +;;; list of lists of the form (FILE-REGEXP BACKUP-DIR OPTIONS ...). If the +;;; filename to be backed up matches FILE-REGEXP, or FILE-REGEXP is t, then +;;; BACKUP-DIR is used as the path for its backups. Directories may begin with +;;; "/" to specify an absolute pathname. If BACKUP-DIR does not exist and +;;; OPTIONS contains the symbol `ok-create', then it is created if possible. +;;; Otherwise the usual behavior (backup in the same directory as the file) +;;; results. If OPTIONS contains the symbol `full-path', then the full path of +;;; the file being backed up is prepended to the backup file name, with each "/" +;;; replaced by a "!". This is intended for cases where an absolute backup path +;;; is used. If OPTIONS contains the symbol `search-upward' and the backup +;;; directory BACKUP-DIR is a relative path, then a directory with that name is +;;; searched for starting at the current directory and proceeding upward (.., +;;; ../.., etc) until one is found of that name or the root is reached, and if +;;; one is found it is used as the backup directory. Finally, if no FILE-REGEXP +;;; matches the file name being backed up, then the usual behavior results. +;;; +;;; These lines from my .emacs load this file and set the values I like: +;;; +;;; (require 'backup-dir) +;;; (setq bkup-backup-directory-info +;;; '(("/home/greg/.*" "/~/.backups/" ok-create full-path) +;;; (t ".backups/" full-path search-upward))) +;;; +;;; +;;; The package also provides a new function, `find-file-latest-backup' to find +;;; the latest backup file for the current buffer's file. +;;; +;;; +;;; This file is based on `files.el' from XEmacs 19.15b4. +;;; It has not been extensively tested on GNU Emacs past 18.58. +;;; It does not work under ms-dos. + + + +(byte-compiler-options + (optimize t) + (warnings (- free-vars)) ; Don't warn about free variables + ) + + +;;; New variables affecting backup file behavior +;;; This is the only user-customizable variable for this package. +;;; +(defvar bkup-backup-directory-info nil + "Alist of (FILE-REGEXP BACKUP-DIR OPTIONS ...)) +If the filename to be backed up matches FILE-REGEXP, or FILE-REGEXP is t, +then BACKUP-DIR is used as the path for its backups. Directories may +begin with \"/\" to specify an absolute pathname. If BACKUP-DIR does +not exist and OPTIONS contains the symbol `ok-create', then it is created if possible. +Otherwise the usual behavior (backup in the same directory as the file) +results. If OPTIONS contains the symbol `full-path', then the full path of the file +being backed up is prepended to the backup file name, with each \"/\" +replaced by a \"!\". This is intended for cases where an absolute backup path +is used. If OPTIONS contains the symbol `search-upward' and the backup +directory BACKUP-DIR is a relative path, then a directory with that name is +searched for starting at the current directory and proceeding upward (.., +../.., etc) until one is found of that name or the root is reached, and if +one is found it is used as the backup directory. Finally, if no FILE-REGEXP +matches the file name being backed up, then the usual behavior results.") + + +;;; New functions +;;; +(defun bkup-search-upward-for-backup-dir (base bd-name) + "search upward for a directory named BD-NAME, starting in the +directory BASE and continuing with its parent directories until +one is found or the root is reached." + (let ((prev nil) (curr base) (gotit nil) (tryit nil)) + (while (and (not gotit) + (not (equal prev curr)) + (not (equal curr "//"))) + (setq prev curr) + (setq curr (expand-file-name (concat curr "../"))) + (setq tryit (expand-file-name bd-name curr)) + (if (and (file-directory-p tryit) (file-exists-p tryit)) + (setq gotit tryit))) + (if (and gotit + (eq (aref gotit (1- (length gotit))) ?/)) + (setq gotit (substring gotit 0 (1- (length gotit))))) + gotit)) + +(defun bkup-replace-slashes-with-exclamations (s) + "Replaces slashes in the string S with exclamations. +A new string is produced and returned." + (let ((ns (copy-sequence s)) + (i (1- (length s)))) + (while (>= i 0) + (if (= (aref ns i) ?/) + (aset ns i ?!)) + (setq i (1- i))) + ns)) + +(defun bkup-try-making-directory (dir) + "try making directory DIR, return non-nil if successful" + (condition-case () + (progn (make-directory dir t) + t) + (t + nil))) + +(defun bkup-backup-basename (file full-path) + "Gives the base part of the backup name for FILE, according to FULL-PATH." + (if full-path + (bkup-replace-slashes-with-exclamations file) + (file-name-nondirectory file))) + +(defun bkup-backup-directory-and-basename (file) + "Return the cons of the backup directory name +and backup file name base for FILE." + (let ((file (expand-file-name file))) + (let ((dir (file-name-directory file)) + (alist bkup-backup-directory-info) + (bk-dir nil) + (bk-base nil)) + (if (listp alist) + (while (and (not bk-dir) alist) + (if (or (eq (car (car alist)) t) + (eq (string-match (car (car alist)) file) 0)) + (let* ((bd (car (cdr (car alist)))) + (bd-rel-p (and (> (length bd) 0) + (not (eq (aref bd 0) ?/)))) + (bd-expn (expand-file-name bd dir)) + (bd-noslash (if (eq (aref bd-expn (1- (length bd-expn))) ?/) + (substring bd-expn 0 (1- (length bd-expn))) + bd-expn)) + (options (cdr (cdr (car alist)))) + (ok-create (and (memq 'ok-create options) t)) + (full-path (and (memq 'full-path options) t)) + (search-upward (and (memq 'search-upward options) t))) + (if bd-expn + (cond ((or (file-directory-p bd-expn) + (and ok-create + (not (file-exists-p bd-expn)) + (bkup-try-making-directory bd-noslash))) + (setq bk-dir (concat bd-noslash "/") + bk-base (bkup-backup-basename file full-path))) + ((and bd-rel-p search-upward) + (let ((bd-up (bkup-search-upward-for-backup-dir dir bd))) + (if bd-up + (setq bk-dir (concat bd-up "/") + bk-base (bkup-backup-basename file full-path))))))))) + (setq alist (cdr alist)))) + (if (and bk-dir bk-base) + (cons bk-dir bk-base) + (cons dir (bkup-backup-basename file nil)))))) + + +;;; This next one is based on the following from `files.el' +;;; but accepts a second optional argument + +;;(defun make-backup-file-name (file) +;; "Create the non-numeric backup file name for FILE. +;;This is a separate function so you can redefine it for customization." +;; (if (and (eq system-type 'ms-dos) +;; (not (msdos-long-file-names))) +;; (let ((fn (file-name-nondirectory file))) +;; (concat (file-name-directory file) +;; (if (string-match "\\([^.]*\\)\\(\\..*\\)?" fn) +;; (substring fn 0 (match-end 1))) +;; ".bak")) +;; (concat file "~"))) + +(defun bkup-make-backup-file-name (file &optional dir-n-base) + "Create the non-numeric backup file name for FILE. +Optionally accept a list containing the backup directory and +backup basename. NB: we don't really handle ms-dos." + (if (and (eq system-type 'ms-dos) + (not (and (fboundp 'msdos-long-file-names) (msdos-long-file-names)))) + (let ((fn (file-name-nondirectory file))) + (concat (file-name-directory file) + (if (string-match "\\([^.]*\\)\\(\\..*\\)?" fn) + (substring fn 0 (match-end 1))) + ".bak")) + (let ((d-n-b (or dir-n-base + (bkup-backup-directory-and-basename file)))) + (concat (car d-n-b) (cdr d-n-b) "~")))) + +(defun bkup-existing-backup-files (fn) + "Return list of existing backup files for file" + (let* ((efn (expand-file-name fn)) + (dir-n-base (bkup-backup-directory-and-basename efn)) + (non-num-bk-name (bkup-make-backup-file-name efn dir-n-base)) + (non-num-bk (file-exists-p non-num-bk-name)) + (backup-dir (car dir-n-base)) + (base-versions (concat (cdr dir-n-base) ".~")) + (possibilities (file-name-all-completions base-versions backup-dir)) + (poss (mapcar #'(lambda (name) (concat backup-dir name)) possibilities))) + (mapcar #'expand-file-name + (if non-num-bk (cons non-num-bk-name poss) poss)))) + +(defun find-file-latest-backup (file) + "Find the latest backup file for FILE" + (interactive (list (read-file-name (format "Find latest backup of file (default %s): " + (file-name-nondirectory (buffer-file-name))) + nil (buffer-file-name) t))) + (let ((backup (file-newest-backup file))) + (if backup + (find-file backup) + (message "no backups found for `%s'" file)))) + + +;;; Functions changed from `files.el' and elsewhere -- originals precede new versions + +;;(defun make-backup-file-name (file) +;; "Create the non-numeric backup file name for FILE. +;;This is a separate function so you can redefine it for customization." +;; (if (and (eq system-type 'ms-dos) +;; (not (msdos-long-file-names))) +;; (let ((fn (file-name-nondirectory file))) +;; (concat (file-name-directory file) +;; (if (string-match "\\([^.]*\\)\\(\\..*\\)?" fn) +;; (substring fn 0 (match-end 1))) +;; ".bak")) +;; (concat file "~"))) + +(defun make-backup-file-name (file) + "Create the non-numeric backup file name for FILE. +This is a separate function so you can redefine it for customization. +*** Changed by \"backup-dir.el\"" + (bkup-make-backup-file-name file)) + + +;;(defun find-backup-file-name (fn) +;; "Find a file name for a backup file, and suggestions for deletions. +;;Value is a list whose car is the name for the backup file +;; and whose cdr is a list of old versions to consider deleting now. +;;If the value is nil, don't make a backup." +;; (let ((handler (find-file-name-handler fn 'find-backup-file-name))) +;; ;; Run a handler for this function so that ange-ftp can refuse to do it. +;; (if handler +;; (funcall handler 'find-backup-file-name fn) +;; (if (eq version-control 'never) +;; (list (make-backup-file-name fn)) +;; (let* ((base-versions (concat (file-name-nondirectory fn) ".~")) +;; ;; used by backup-extract-version: +;; (bv-length (length base-versions)) +;; possibilities +;; (versions nil) +;; (high-water-mark 0) +;; (deserve-versions-p nil) +;; (number-to-delete 0)) +;; (condition-case () +;; (setq possibilities (file-name-all-completions +;; base-versions +;; (file-name-directory fn)) +;; versions (sort (mapcar +;; #'backup-extract-version +;; possibilities) +;; '<) +;; high-water-mark (apply #'max 0 versions) +;; deserve-versions-p (or version-control +;; (> high-water-mark 0)) +;; number-to-delete (- (length versions) +;; kept-old-versions kept-new-versions -1)) +;; (file-error +;; (setq possibilities nil))) +;; (if (not deserve-versions-p) +;; (list (make-backup-file-name fn)) +;; (cons (concat fn ".~" (int-to-string (1+ high-water-mark)) "~") +;; (if (and (> number-to-delete 0) +;; ;; Delete nothing if there is overflow +;; ;; in the number of versions to keep. +;; (>= (+ kept-new-versions kept-old-versions -1) 0)) +;; (mapcar #'(lambda (n) +;; (concat fn ".~" (int-to-string n) "~")) +;; (let ((v (nthcdr kept-old-versions versions))) +;; (rplacd (nthcdr (1- number-to-delete) v) ()) +;; v)))))))))) + +(defun find-backup-file-name (fn) + "Find a file name for a backup file, and suggestions for deletions. +Value is a list whose car is the name for the backup file + and whose cdr is a list of old versions to consider deleting now. +If the value is nil, don't make a backup. +*** Changed by \"backup-dir.el\"" + (let ((handler (find-file-name-handler fn 'find-backup-file-name))) + ;; Run a handler for this function so that ange-ftp can refuse to do it. + (if handler + (funcall handler 'find-backup-file-name fn) + (if (eq version-control 'never) + (list (make-backup-file-name fn)) + (let* ((dir-n-base (bkup-backup-directory-and-basename fn)) ;add + (non-num-bk-name (bkup-make-backup-file-name fn dir-n-base)) ;add + (bk-dir (car dir-n-base)) ;add + (bk-base (cdr dir-n-base)) ;add + (base-versions (concat bk-base ".~")) ;mod + ;; used by backup-extract-version: + (bv-length (length base-versions)) + possibilities + (versions nil) + (high-water-mark 0) + (deserve-versions-p nil) + (number-to-delete 0)) + (condition-case () + (setq possibilities (file-name-all-completions + base-versions + bk-dir) ;mod + versions (sort (mapcar + #'backup-extract-version + possibilities) + '<) + high-water-mark (apply #'max 0 versions) + deserve-versions-p (or version-control + (> high-water-mark 0)) + number-to-delete (- (length versions) + kept-old-versions kept-new-versions -1)) + (file-error + (setq possibilities nil))) + (if (not deserve-versions-p) + (list (bkup-make-backup-file-name fn dir-n-base)) ;mod + (cons (concat bk-dir base-versions (int-to-string (1+ high-water-mark)) "~") ;mod + (if (and (> number-to-delete 0) + ;; Delete nothing if there is overflow + ;; in the number of versions to keep. + (>= (+ kept-new-versions kept-old-versions -1) 0)) + (mapcar #'(lambda (n) + (concat bk-dir base-versions (int-to-string n) "~")) ;mod + (let ((v (nthcdr kept-old-versions versions))) + (rplacd (nthcdr (1- number-to-delete) v) ()) + v)))))))))) + + +;;(defun file-newest-backup (filename) +;; "Return most recent backup file for FILENAME or nil if no backups exist." +;; (let* ((filename (expand-file-name filename)) +;; (file (file-name-nondirectory filename)) +;; (dir (file-name-directory filename)) +;; (comp (file-name-all-completions file dir)) +;; newest tem) +;; (while comp +;; (setq tem (car comp) +;; comp (cdr comp)) +;; (cond ((and (backup-file-name-p tem) +;; (string= (file-name-sans-versions tem) file)) +;; (setq tem (concat dir tem)) +;; (if (or (null newest) +;; (file-newer-than-file-p tem newest)) +;; (setq newest tem))))) +;; newest)) + +(defun file-newest-backup (filename) + "Return most recent backup file for FILENAME or nil if no backups exist. +*** Changed by \"backup-dir.el\"" + (let ((comp (bkup-existing-backup-files filename)) + (newest nil) + (file nil)) + (while comp + (setq file (car comp) + comp (cdr comp)) + (if (and (backup-file-name-p file) + (or (null newest) (file-newer-than-file-p file newest))) + (setq newest file))) + newest)) + + +;;; patch `latest-backup-file' from "dired" +;;; +;;; we use `dired-load-hook' to avoid loading dired now. This speeds things up +;;; considerably according to Thomas Feuster, feuster@tp4.physik.uni-giessen.de +;;; +;;; one really wonders why there are 3 functions to do the same thing... +;;; +(defun bkup-patch-latest-backup-file () + (fset 'latest-backup-file (symbol-function 'file-newest-backup)) + (remove-hook 'dired-load-hook 'bkup-patch-latest-backup-file)) + +(if (featurep 'dired) + ;; if loaded, patch it now + (fset 'latest-backup-file (symbol-function 'file-newest-backup)) + ;; otherwise do it later + (add-hook 'dired-load-hook 'bkup-patch-latest-backup-file)) + + +;;; patch `diff-latest-backup-file' from "diff" +;;; +(require 'diff) +(fset 'diff-latest-backup-file (symbol-function 'file-newest-backup)) + + +;;; finally, add to list of features +;;; +(provide 'backup-dir) + +;;; backup-dir.el ends here