*** empty log message ***
[bpt/emacs.git] / lisp / vc-hooks.el
index 33ec82a..64a7d8f 100644 (file)
@@ -1,18 +1,18 @@
 ;;; vc-hooks.el --- resident support for version-control
 
-;; Copyright (C) 1992,93,94,95,96,98,99,2000,2003
-;;           Free Software Foundation, Inc.
+;; Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
+;;   2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
 
 ;; Author:     FSF (see vc.el for full credits)
 ;; Maintainer: Andre Spiegel <spiegel@gnu.org>
 
-;; $Id: vc-hooks.el,v 1.158 2003/08/23 23:04:20 rost Exp $
+;; $Id$
 
 ;; This file is part of GNU Emacs.
 
 ;; GNU Emacs 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 2, or (at your option)
+;; the Free Software Foundation; either version 3, or (at your option)
 ;; any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
@@ -22,8 +22,8 @@
 
 ;; You should have received a copy of the GNU General Public License
 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
 
 ;;; Commentary:
 
                         "set `vc-handled-backends' to nil to disable VC.")
 
 (defvar vc-master-templates ())
-(make-obsolete-variable 'vc-master-templates 
- "to define master templates for a given BACKEND, use 
+(make-obsolete-variable 'vc-master-templates
+ "to define master templates for a given BACKEND, use
 vc-BACKEND-master-templates.  To enable or disable VC for a given
 BACKEND, use `vc-handled-backends'.")
 
 (defvar vc-header-alist ())
 (make-obsolete-variable 'vc-header-alist 'vc-BACKEND-header)
 
-(defcustom vc-handled-backends '(RCS CVS SVN MCVS SCCS)
-  "*List of version control backends for which VC will be used.
+(defcustom vc-ignore-dir-regexp
+  ;; Stop SMB, automounter, AFS, and DFS host lookups.
+  "\\`\\(?:[\\/][\\/]\\|/\\(?:net\\|afs\\|\\.\\\.\\.\\)/\\)\\'"
+  "Regexp matching directory names that are not under VC's control.
+The default regexp prevents fruitless and time-consuming attempts
+to determine the VC status in directories in which filenames are
+interpreted as hostnames."
+  :type 'regexp
+  :group 'vc)
+
+(defcustom vc-handled-backends '(RCS CVS SVN SCCS Bzr Git Hg Mtn Arch MCVS)
+  ;; RCS, CVS, SVN and SCCS come first because they are per-dir
+  ;; rather than per-tree.  RCS comes first because of the multibackend
+  ;; support intended to use RCS for local commits (with a remote CVS server).
+  "List of version control backends for which VC will be used.
 Entries in this list will be tried in order to determine whether a
 file is under that sort of version control.
 Removing an entry from the list prevents VC from being activated
 when visiting a file managed by that backend.
 An empty list disables VC altogether."
   :type '(repeat symbol)
-  :version "21.1"
+  :version "22.2"
   :group 'vc)
 
 (defcustom vc-path
@@ -142,7 +155,7 @@ by these regular expressions."
                (set :format "%v" :inline t (const :format "%t" :tag "don't" except))
                (regexp :format " stay local,\n%t: %v" :tag "if it matches")
                (repeat :format "%v%i\n" :inline t (regexp :tag "or"))))
-  :version "21.4"
+  :version "22.1"
   :group 'vc)
 
 (defun vc-stay-local-p (file)
@@ -265,7 +278,6 @@ It is usually called via the `vc-call' macro."
 (defmacro vc-call (fun file &rest args)
   ;; BEWARE!! `file' is evaluated twice!!
   `(vc-call-backend (vc-backend ,file) ',fun ,file ,@args))
-
 \f
 (defsubst vc-parse-buffer (pattern i)
   "Find PATTERN in the current buffer and return its Ith submatch."
@@ -298,6 +310,33 @@ non-nil if FILE exists and its contents were successfully inserted."
     (set-buffer-modified-p nil)
     t))
 
+(defun vc-find-root (file witness)
+  "Find the root of a checked out project.
+The function walks up the directory tree from FILE looking for WITNESS.
+If WITNESS if not found, return nil, otherwise return the root."
+  ;; Represent /home/luser/foo as ~/foo so that we don't try to look for
+  ;; witnesses in /home or in /.
+  (while (not (file-directory-p file))
+    (setq file (file-name-directory (directory-file-name file))))
+  (setq file (abbreviate-file-name file))
+  (let ((root nil)
+        (user (nth 2 (file-attributes file))))
+    (while (not (or root
+                    (null file)
+                    ;; As a heuristic, we stop looking up the hierarchy of
+                    ;; directories as soon as we find a directory belonging
+                    ;; to another user.  This should save us from looking in
+                    ;; things like /net and /afs.  This assumes that all the
+                    ;; files inside a project belong to the same user.
+                    (not (equal user (nth 2 (file-attributes file))))
+                    (string-match vc-ignore-dir-regexp file)))
+      (if (file-exists-p (expand-file-name witness file))
+          (setq root file)
+        (if (equal file
+                   (setq file (file-name-directory (directory-file-name file))))
+            (setq file nil))))
+    root))
+
 ;; Access functions to file properties
 ;; (Properties should be _set_ using vc-file-setprop, but
 ;; _retrieved_ only through these functions, which decide
@@ -315,11 +354,13 @@ on the result of a previous call, use `vc-backend' instead.  If the
 file was previously registered under a certain backend, then that
 backend is tried first."
   (let (handler)
-    (if (boundp 'file-name-handler-alist)
-       (setq handler (find-file-name-handler file 'vc-registered)))
-    (if handler
-        ;; handler should set vc-backend and return t if registered
-       (funcall handler 'vc-registered file)
+    (cond
+     ((string-match vc-ignore-dir-regexp (file-name-directory file)) nil)
+     ((and (boundp 'file-name-handler-alist)
+          (setq handler (find-file-name-handler file 'vc-registered)))
+      ;; handler should set vc-backend and return t if registered
+      (funcall handler 'vc-registered file))
+     (t
       ;; There is no file name handler.
       ;; Try vc-BACKEND-registered for each handled BACKEND.
       (catch 'found
@@ -334,7 +375,7 @@ backend is tried first."
             (cons backend vc-handled-backends))))
         ;; File is not registered.
         (vc-file-setprop file 'vc-backend 'none)
-        nil))))
+        nil)))))
 
 (defun vc-backend (file)
   "Return the version control type of FILE, nil if it is not registered."
@@ -388,14 +429,22 @@ For registered files, the possible values are:
           (vc-file-setprop file 'vc-checkout-model
                            (vc-call checkout-model file)))))
 
-(defun vc-user-login-name (&optional uid)
-  "Return the name under which the user is logged in, as a string.
-\(With optional argument UID, return the name of that user.)
-This function does the same as function `user-login-name', but unlike
-that, it never returns nil.  If a UID cannot be resolved, that
-UID is returned as a string."
-  (or (user-login-name uid)
-      (number-to-string (or uid (user-uid)))))
+(defun vc-user-login-name (file)
+  "Return the name under which the user accesses the given FILE."
+  (or (and (eq (string-match tramp-file-name-regexp file) 0)
+           ;; tramp case: execute "whoami" via tramp
+           (let ((default-directory (file-name-directory file)))
+             (with-temp-buffer
+               (if (not (zerop (process-file "whoami" nil t)))
+                   ;; fall through if "whoami" didn't work
+                   nil
+                 ;; remove trailing newline
+                 (delete-region (1- (point-max)) (point-max))
+                 (buffer-string)))))
+      ;; normal case
+      (user-login-name)
+      ;; if user-login-name is nil, return the UID as a string
+      (number-to-string (user-uid))))
 
 (defun vc-state (file)
   "Return the version control state of FILE.
@@ -439,6 +488,12 @@ For registered files, the value returned is one of:
           (vc-file-setprop file 'vc-state
                            (vc-call state-heuristic file)))))
 
+(defun vc-recompute-state (file)
+  "Recompute the version control state of FILE, and return it.
+This calls the possibly expensive function vc-BACKEND-state,
+rather than the heuristic."
+  (vc-file-setprop file 'vc-state (vc-call state file)))
+
 (defsubst vc-up-to-date-p (file)
   "Convenience function that checks whether `vc-state' of FILE is `up-to-date'."
   (eq (vc-state file) 'up-to-date))
@@ -453,7 +508,9 @@ and does not employ any heuristic at all."
   "Return non-nil if FILE has not changed since the last checkout."
   (let ((checkout-time (vc-file-getprop file 'vc-checkout-time))
         (lastmod (nth 5 (file-attributes file))))
-    (if checkout-time
+    (if (and checkout-time
+             ;; Tramp and Ange-FTP return this when they don't know the time.
+             (not (equal lastmod '(0 0))))
         (equal checkout-time lastmod)
       (let ((unchanged (vc-call workfile-unchanged-p file)))
         (vc-file-setprop file 'vc-checkout-time (if unchanged lastmod 0))
@@ -462,8 +519,21 @@ and does not employ any heuristic at all."
 (defun vc-default-workfile-unchanged-p (backend file)
   "Check if FILE is unchanged by diffing against the master version.
 Return non-nil if FILE is unchanged."
-  ;; If rev1 is nil, `diff' uses the current workfile version.
-  (zerop (vc-call diff file)))
+  (zerop (condition-case err
+             ;; If the implementation supports it, let the output
+             ;; go to *vc*, not *vc-diff*, since this is an internal call.
+             (vc-call diff file nil nil "*vc*")
+           (wrong-number-of-arguments
+            ;; If this error came from the above call to vc-BACKEND-diff,
+            ;; try again without the optional buffer argument (for
+            ;; backward compatibility).  Otherwise, resignal.
+            (if (or (not (eq (cadr err)
+                             (indirect-function
+                              (vc-find-backend-function (vc-backend file)
+                                                        'diff))))
+                    (not (eq (caddr err) 4)))
+                (signal (car err) (cdr err))
+              (vc-call diff file))))))
 
 (defun vc-workfile-version (file)
   "Return the version level of the current workfile FILE.
@@ -569,9 +639,10 @@ the user should be returned; if REGEXP is non-nil that means to return
 a regexp for matching all such backup files, regardless of the version."
   (if regexp
       (concat (regexp-quote (file-name-nondirectory file))
-              "\\.~[0-9.]+" (unless manual "\\.") "~")
+              "\\.~.+" (unless manual "\\.") "~")
     (expand-file-name (concat (file-name-nondirectory file)
-                              ".~" (or rev (vc-workfile-version file))
+                              ".~" (subst-char-in-string
+                                    ?/ ?_ (or rev (vc-workfile-version file)))
                               (unless manual ".") "~")
                       (file-name-directory file))))
 
@@ -591,8 +662,15 @@ Before doing that, check if there are any old backups and get rid of them."
   (unless (and (fboundp 'msdos-long-file-names)
                (not (with-no-warnings (msdos-long-file-names))))
     (vc-delete-automatic-version-backups file)
-    (copy-file file (vc-version-backup-file-name file)
-               nil 'keep-date)))
+    (condition-case nil
+        (copy-file file (vc-version-backup-file-name file)
+                   nil 'keep-date)
+      ;; It's ok if it doesn't work (e.g. directory not writable),
+      ;; since this is just for efficiency.
+      (file-error
+       (message
+        (concat "Warning: Cannot make version backup; "
+                "diff/revert therefore not local"))))))
 
 (defun vc-before-save ()
   "Function to be called by `basic-save-buffer' (in files.el)."
@@ -629,6 +707,21 @@ Before doing that, check if there are any old backups and get rid of them."
             ;; any VC Dired buffer to synchronize.
             (vc-dired-resynch-file file)))))
 
+(defvar vc-menu-entry
+  '(menu-item "Version Control" vc-menu-map
+    :filter vc-menu-map-filter))
+
+(when (boundp 'menu-bar-tools-menu)
+  ;; We do not need to worry here about the placement of this entry
+  ;; because menu-bar.el has already created the proper spot for us
+  ;; and this will simply use it.
+  (define-key menu-bar-tools-menu [vc] vc-menu-entry))
+
+(defconst vc-mode-line-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map [mode-line down-mouse-1] vc-menu-entry)
+    map))
+
 (defun vc-mode-line (file)
   "Set `vc-mode' to display type of version control for FILE.
 The value is set in the current buffer, which should be the buffer
@@ -698,6 +791,9 @@ current, and kill the buffer that visits the link."
       (set-buffer true-buffer)
       (kill-buffer this-buffer))))
 
+(defun vc-default-find-file-hook (backend)
+  nil)
+
 (defun vc-find-file-hook ()
   "Function for `find-file-hook' activating VC mode if appropriate."
   ;; Recompute whether file is version controlled,
@@ -707,15 +803,17 @@ current, and kill the buffer that visits the link."
   (when buffer-file-name
     (vc-file-clearprops buffer-file-name)
     (cond
-     ((vc-backend buffer-file-name)
+     ((ignore-errors (vc-backend buffer-file-name))
       ;; Compute the state and put it in the modeline.
       (vc-mode-line buffer-file-name)
       (unless vc-make-backup-files
        ;; Use this variable, not make-backup-files,
        ;; because this is for things that depend on the file name.
-       (set (make-local-variable 'backup-inhibited) t)))
-     ((let* ((link (file-symlink-p buffer-file-name))
-            (link-type (and link (vc-backend (file-chase-links link)))))
+       (set (make-local-variable 'backup-inhibited) t))
+      ;; Let the backend setup any buffer-local things he needs.
+      (vc-call-backend (vc-backend buffer-file-name) 'find-file-hook))
+     ((let ((link-type (and (file-symlink-p buffer-file-name)
+                           (vc-backend (file-chase-links buffer-file-name)))))
        (cond ((not link-type) nil)     ;Nothing to do.
              ((eq vc-follow-symlinks nil)
               (message
@@ -752,14 +850,13 @@ Used in `find-file-not-found-functions'."
   ;; When a file does not exist, ignore cached info about it
   ;; from a previous visit.
   (vc-file-clearprops buffer-file-name)
-  (if (and (vc-backend buffer-file-name)
-          (yes-or-no-p
-           (format "File %s was lost; check out from version control? "
-                   (file-name-nondirectory buffer-file-name))))
-    (save-excursion
-      (require 'vc)
-      (setq default-directory (file-name-directory buffer-file-name))
-      (not (vc-error-occurred (vc-checkout buffer-file-name))))))
+  (let ((backend (vc-backend buffer-file-name)))
+    (if backend (vc-call-backend backend 'find-file-not-found-hook))))
+
+(defun vc-default-find-file-not-found-hook (backend)
+  ;; This used to do what vc-rcs-find-file-not-found-hook does, but it only
+  ;; really makes sense for RCS.  For other backends, better not do anything.
+  nil)
 
 (add-hook 'find-file-not-found-functions 'vc-file-not-found-hook)
 
@@ -798,36 +895,54 @@ Used in `find-file-not-found-functions'."
 (fset 'vc-prefix-map vc-prefix-map)
 (define-key global-map "\C-xv" 'vc-prefix-map)
 
-(if (not (boundp 'vc-menu-map))
-    ;; Don't do the menu bindings if menu-bar.el wasn't loaded to defvar
-    ;; vc-menu-map.
-    ()
-  ;;(define-key vc-menu-map [show-files]
-  ;;  '("Show Files under VC" . (vc-directory t)))
-  (define-key vc-menu-map [vc-retrieve-snapshot]
-    '("Retrieve Snapshot" . vc-retrieve-snapshot))
-  (define-key vc-menu-map [vc-create-snapshot]
-    '("Create Snapshot" . vc-create-snapshot))
-  (define-key vc-menu-map [vc-directory] '("VC Directory Listing" . vc-directory))
-  (define-key vc-menu-map [separator1] '("----"))
-  (define-key vc-menu-map [vc-annotate] '("Annotate" . vc-annotate))
-  (define-key vc-menu-map [vc-rename-file] '("Rename File" . vc-rename-file))
-  (define-key vc-menu-map [vc-version-other-window]
-    '("Show Other Version" . vc-version-other-window))
-  (define-key vc-menu-map [vc-diff] '("Compare with Base Version" . vc-diff))
-  (define-key vc-menu-map [vc-update-change-log]
-    '("Update ChangeLog" . vc-update-change-log))
-  (define-key vc-menu-map [vc-print-log] '("Show History" . vc-print-log))
-  (define-key vc-menu-map [separator2] '("----"))
-  (define-key vc-menu-map [vc-insert-header]
-    '("Insert Header" . vc-insert-headers))
-  (define-key vc-menu-map [undo] '("Undo Last Check-In" . vc-cancel-version))
-  (define-key vc-menu-map [vc-revert-buffer]
-    '("Revert to Base Version" . vc-revert-buffer))
-  (define-key vc-menu-map [vc-update]
-    '("Update to Latest Version" . vc-update))
-  (define-key vc-menu-map [vc-next-action] '("Check In/Out" . vc-next-action))
-  (define-key vc-menu-map [vc-register] '("Register" . vc-register)))
+(defvar vc-menu-map
+  (let ((map (make-sparse-keymap "Version Control")))
+    ;;(define-key map [show-files]
+    ;;  '("Show Files under VC" . (vc-directory t)))
+    (define-key map [vc-retrieve-snapshot]
+      '("Retrieve Snapshot" . vc-retrieve-snapshot))
+    (define-key map [vc-create-snapshot]
+      '("Create Snapshot" . vc-create-snapshot))
+    (define-key map [vc-directory] '("VC Directory Listing" . vc-directory))
+    (define-key map [separator1] '("----"))
+    (define-key map [vc-annotate] '("Annotate" . vc-annotate))
+    (define-key map [vc-rename-file] '("Rename File" . vc-rename-file))
+    (define-key map [vc-version-other-window]
+      '("Show Other Version" . vc-version-other-window))
+    (define-key map [vc-diff] '("Compare with Base Version" . vc-diff))
+    (define-key map [vc-update-change-log]
+      '("Update ChangeLog" . vc-update-change-log))
+    (define-key map [vc-print-log] '("Show History" . vc-print-log))
+    (define-key map [separator2] '("----"))
+    (define-key map [vc-insert-header]
+      '("Insert Header" . vc-insert-headers))
+    (define-key map [undo] '("Undo Last Check-In" . vc-cancel-version))
+    (define-key map [vc-revert-buffer]
+      '("Revert to Base Version" . vc-revert-buffer))
+    (define-key map [vc-update]
+      '("Update to Latest Version" . vc-update))
+    (define-key map [vc-next-action] '("Check In/Out" . vc-next-action))
+    (define-key map [vc-register] '("Register" . vc-register))
+    map))
+
+(defalias 'vc-menu-map vc-menu-map)
+
+(defun vc-menu-map-filter (orig-binding)
+  (if (and (symbolp orig-binding) (fboundp orig-binding))
+      (setq orig-binding (indirect-function orig-binding)))
+  (let ((ext-binding
+         (if vc-mode (vc-call-backend (vc-backend buffer-file-name)
+                                      'extra-menu))))
+    ;; Give the VC backend a chance to add menu entries
+    ;; specific for that backend.
+    (if (null ext-binding)
+        orig-binding
+      (append orig-binding
+             '((ext-menu-separator "---"))
+              ext-binding))))
+
+(defun vc-default-extra-menu (backend)
+  nil)
 
 ;; These are not correct and it's not currently clear how doing it
 ;; better (with more complicated expressions) might slow things down
@@ -848,4 +963,5 @@ Used in `find-file-not-found-functions'."
 
 (provide 'vc-hooks)
 
+;; arch-tag: 2e5a6fa7-1d30-48e2-8bd0-e3d335f04f32
 ;;; vc-hooks.el ends here